Compare commits

...

160 Commits

Author SHA1 Message Date
Brian Ford e6ece7d993 docs(changelog): more release notes for 1.3.0-rc.5 2014-10-08 15:51:30 -07:00
Shahar Talmi 0563a0a636 refactor(formController): remove redundant methods from nullFormCtrl 2014-10-08 15:35:55 -07:00
Tobias Bosch f4ff11b01e feat($route): ability to cancel $routeChangeStart event
Calling `preventDefault()` on a `$routeChangeStart` event will
prevent the route change and also call `preventDefault` on the `$locationChangeStart` event, which prevents the location change as well.

BREAKING CHANGE:

Order of events has changed.
Previously: `$locationChangeStart` -> `$locationChangeSuccess`
  -> `$routeChangeStart` -> `$routeChangeSuccess`

Now: `$locationChangeStart` -> `$routeChangeStart`
  -> `$locationChangeSuccess` ->  -> `$routeChangeSuccess`

Fixes #5581
Closes #5714
Closes #9502
2014-10-08 15:35:04 -07:00
Caitlin Potter 0d3b69a5f2 fix($injector): throw when factory $get method does not return a value
BREAKING CHANGE:

Previously, not returning a value would fail silently, and an application trying to inject the
value owuld inject an undefined value, quite possibly leading to a TypeError. Now, the application
will fail entirely, and a reason will be given.

Closes #4575
Closes #9210
2014-10-08 16:49:38 -04:00
Matias Niemelä 39d0b36826 fix($animate): ensure hidden elements with ngShow/ngHide stay hidden during animations
Prior to this fix if an element that contained ng-show or ng-hide was in its hidden state
then any other animation run on the same element would cause the animation to appear despite
the element itself already being hidden. This patch ensures that NO animations are visible
even if the element is set as hidden.

Closes #9103
Closes #9493
2014-10-08 13:48:25 -07:00
Matias Niemelä 035ffb82c4 chore($animate): enable temporary classes to be applied during an animation
Closes #9493
2014-10-08 13:48:12 -07:00
Igor Minar d5445c601f fix($anchorScroll): don't scroll to top when initializing and location hash is empty
Closes #8848
Closes #9393
2014-10-08 13:40:46 -07:00
Matias Niemelä df1a00b11a fix($animate): permit class-based animations for leave operations if ngAnimateChildren is enabled
Prior to this fix, $animate.leave placed a disabled animation on the element
which prevented ngAnimateChildren from properly working. This patch now
addresses that issue.

Closes #8092
Closes #9491
2014-10-08 13:37:54 -07:00
Brian Ford d9ff4e42ce docs(changelog): release notes for 1.3.0-rc.5 2014-10-08 13:37:01 -07:00
Sean Griffin b3e09be589 feat($location): allow automatic rewriting of links to be disabled
Currently, when the location provider is set to html5 mode, all links
on the page are hijacked and automatically rewritten. While this may be
desirable behavior in some cases (such as using ngRoute), not all cases
where html5 mode are enabled imply the desire for this behavior.

One example would be an application using the
[ui-router](https://github.com/angular-ui/ui-router) library, with some
pages that exist outside of angular. Links that are meant to go through
the router use the `ui-sref` directive, so the rewrite behavior is
unnecessary.

Closes #5487
2014-10-08 13:11:52 -07:00
Caitlin Potter 0c2378de79 refactor(ngAnimate): remove unneeded comparison
It was supposed to be removed in 22358cf9c7, but was not.
2014-10-08 16:05:13 -04:00
Jeff Cross 30996f82af fix(select): throw for selectAs and trackBy
trackBy and selectAs have never worked together, and are fundamentally
incompatible since model changes cannot deterministically be
reflected back to the view. This change throws an error to help
developers better understand this scenario.
2014-10-08 12:46:26 -07:00
Tobias Bosch aad60953ce refactor(select): reduce duplication and reorder functions 2014-10-08 12:27:15 -07:00
Jeff Cross ab354cf04e fix(select): make ngOptions support selectAs and trackBy together
This commit implements two functions, "isSelected()" and "getViewValue()"
to properly compute an option's selected state and the model controller's
viewValue respectively. These functions give proper precedence to "track by"
and "select as" parts of the ngOptions comprehension expression, which were
previously inconsistent and incompatible.

Fixes #6564
2014-10-08 12:27:15 -07:00
Caitlin Potter 208114c2b2 chore(AngularPublic): remove $$hasClass from angular exports
It was previously used for ngAnimate, but is no longer needed
2014-10-08 14:56:50 -04:00
Caitlin Potter 22358cf9c7 perf($animate): access DOM less in resolveElementClasses
Previously we were reading DOM attributes frequently, now we can do it just once.
2014-10-08 14:56:49 -04:00
Caitlin Potter 003c44ecee perf($animate): don't join classes before it's necessary in resolveElementClasses
In ngAnimate, we can't do this because the behaviour is exposed via the API. But
in core, we can avoid a bit of work.
2014-10-08 14:56:49 -04:00
Caitlin Potter 9e4701a5ab chore(ngAnimate): add TODO messages indicating desire to remove hack 2014-10-08 14:56:49 -04:00
Caitlin Potter cae01f4941 refactor($compile): use labaled variables to represent nodeType values
This also does some cleanup in $animate
2014-10-08 14:56:49 -04:00
Caitlin Potter 667183a8c7 fix(ngAnimate): defer DOM operations for changing classes to postDigest
When ngAnimate is used, it will defer changes to classes until postDigest. Previously,
AngularJS (when ngAnimate is not loaded) would always immediately perform these DOM
operations.

Now, even when the ngAnimate module is not used, if $rootScope is in the midst of a
digest, class manipulation is deferred. This helps reduce jank in browsers such as
IE11.

BREAKING CHANGE:

The $animate class API will always defer changes until the end of the next digest. This allows ngAnimate
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
many. This prevents jank in browsers such as IE, and is generally a good thing.

If you're finding that your classes are not being immediately applied, be sure to invoke $digest().

Closes #8234
Closes #9263
2014-10-08 14:56:49 -04:00
Caitlin Potter 35049be9d9 test(matchers.js): make toHaveClass matcher work better for SVG+jQuery 2014-10-08 14:56:49 -04:00
Izhaki 323862ff63 docs($compile): note template is ignored with element transclusion 2014-10-08 11:27:45 -07:00
Michał Gołębiowski e63f670ff2 docs(content): update Angular 1.2 mentions to Angular 1.3
Angular 1.3 docs now describe the process of using this version instead of
the older 1.2 that is the latest stable version.

Also, update jQuery 1.10.x mentions to 2.1.x.
2014-10-08 10:59:54 -07:00
Jesse Palmer 6b786dcb57 docs(ngIf): update out-of-date link 2014-10-08 10:42:41 -07:00
Michał Gołębiowski 1beebee968 chore($sniffer): Remove $sniffer.hashchange
The hashchange event is not supported only in ancient browsers like Android<2.2
and IE<8. Angular never really supported IE7 and in 1.3 where support for IE8
is dropped it makes even less sense to check for hashchange support.
2014-10-08 17:36:44 +02:00
Caitlin Potter 74a214c043 chore(ngScenario): make jshint happy
Someone accidentally removed an important keyword which prevented a variable from being added to the global object

Closes #9484
2014-10-07 22:54:44 -04:00
Matias Niemelä c93924ed27 fix($animate): ensure that class-based animations only consider the most recent DOM operations
Prior to this fix $animate would maintain a count of each time a class was
added and removed within $animate. With this fix, $animate instead only cares
about the most recent addClass or removeClass operation and will only perform
that operation (depending on what was last called).

```
// before
addClass    => +1
removeClass => 0
addClass    => +1
addClass    => +2
removeClass => +1
// this will cause an addClass animation

// now
addClass    => add
removeClass => remove
addClass    => add
addClass    => add
removeClass => remove
// this will cause a removeClass animation
```

Closes #8946
Closes #9458
2014-10-07 16:21:56 -07:00
Igor Minar a84480affb refactor: use document.documentMode to determine msie version
Closes #9398
2014-10-07 16:09:50 -07:00
Michał Gołębiowski 6fd36deed9 feat($location): add support for History API state handling
Adds $location state method allowing to get/set a History API state via
pushState & replaceState methods.

Note that:
- Angular treats states undefined and null as the same; trying to change
one to the other without touching the URL won't do anything. This is necessary
to prevent infinite digest loops when setting the URL to itself in IE<10 in
the HTML5 hash fallback mode.
- The state() method is not compatible with browsers not supporting
the HTML5 History API, e.g. IE 9 or Android < 4.0.

Closes #9027
2014-10-07 15:48:10 -07:00
Tobias Bosch 8ee1ba4b94 fix($browser): Cache location.href only during page reload phase
Adds caching for url changes while a reload is happening,
as browsers do not allow to read out the new location the browser
is navigating to.

Removes unnecessary caching from $browser, as IE7-IE9 all
have the new hash value in `location.href` after changing it.
There was a wrong assumption in the previous version of this code
introduced by dca23173e2 and d70711481e.

Adds more tests for #6976
Fixes #9235
Closes #9455
2014-10-07 11:20:33 -07:00
Ben Harold ab240196bf docs($injector): fix syntax error
There was a missing parenthesis in $injector test example

Closes #9469
2014-10-07 13:39:09 -04:00
Matias Niemelä 613d0a3212 fix($animate): abort class-based animations if the element is removed during digest
Prior to this fix, if the element is removed before the digest kicks off then it leads
to an error when a class based animation is run. This fix ensures that the animation will
not run at all if the element does not have a parent element.

Closes #8796
2014-10-07 11:03:16 +03:00
Matias Niemelä cb85cbcec1 fix($animate): clear the GCS cache even when no animation is detected
$animate will cache subsequent calls to GCS in the event that the element
with the same CSS classes and the same parentNode is being animated. Once the
animation is started then $animate waits for one rAF before flushing the GCS
lookup cache. Prior to this fix, if GCS was unable to detect any transitions
or keyframes on the element then it would simply close the animation, but it
would not trigger the rAF code to flush the cache. This issue caused a bug
which made it difficult to detect why certain animations are not allowed to
fire if the element didn't contain any CSS-based animations beforehand.

Closes #8813
2014-10-07 09:32:29 +03:00
Lucas Galfaso a75546afdf fix($compile): Handle the removal of an interpolated attribute
Handle the removal of an interpolated attribute before the
attribute interpolating directive is linked

Closes #9236
Closes #9240
2014-10-06 22:22:49 -07:00
Lucas Galfaso e843ae7a4c chore(IE8): remove all special code for IE8
Remove all code that was IE8 specific

Closes #8837
2014-10-06 17:04:09 -07:00
Rouven Weßling 40d8da80ce refactor($http) Use onload/onerror/onabort instead of onreadystatechange
Closes #9329
2014-10-06 17:01:42 -07:00
Rouven Weßling f52203ce71 refactor($http) Simplify code by removing workarounds for older versions of Internet Explorer
This removes a workaround for IE 8 and and error handling for IE6.

Closes #9300
2014-10-06 16:33:19 -07:00
Casey Garland d9457aa288 docs(guide/index): fix broken link 2014-10-06 16:01:49 -07:00
Jesse Palmer 46db47bb37 chore(publish.sh): fix url in script console output 2014-10-06 15:52:40 -07:00
thorn0 0e4390c923 docs($injector): injectors aren't functions
Closes #9453
2014-10-06 18:19:23 -04:00
thorn0 c6d76d1e2e docs($injector): clean up docs for $injector#has
Fixup return type and param info.

Closes #9452
2014-10-06 16:52:39 -04:00
tommyangelo da366f7e92 docs(ngBindHtml): explain that angular-sanitize.js is needed to depend on ngSanitize
Added comment. You need to include angular-sanitize.js otherwise you cant use ngSanitize!

Closes #9400
2014-10-06 16:47:32 -04:00
Agam Rafaeli 3ad774b01c docs(guide/introduction): guice has moved to github
Closes #9416
2014-10-06 16:31:11 -04:00
Jesse Palmer 7911c3cfe7 docs(ngIf): wrap ngIf in code tags
Closes #9435
2014-10-06 16:10:12 -04:00
erikrahm 2cb0b309cb docs(misc/faq): grammatical error fixes
Merci beaucoup

Closes #9451
Closes #9450
2014-10-06 16:05:41 -04:00
thorn0 8ef69d2d7a docs($injector): $injector isn't a function
Closes #9448
2014-10-06 20:21:12 +01:00
Tim Kendrick 687981913c chore(docs): remove unused gruntUtils import from docs config
As of commit 11c5bb7, the gruntUtils import is no longer needed in the
gitData docs config service.

Closes #9446
2014-10-06 20:19:15 +01:00
Bijan Boustani 806ef998be docs(guide/introduction): fix grammar style
Changed "you would currently have to write" to "you would otherwise have to write".

Seems to make more sense this way since "currently" presupposes that someone new
to Angular would be coming from a different paradigm, which they may or may not be.

Closes #9428
2014-10-06 20:10:08 +01:00
Brian Ford a21d49c900 chore(npm): publish artifacts to npm
Closes #2877
2014-10-06 11:39:03 -07:00
Peter Bacon Darwin 43d4fffdbe chore(docs): update to dgeni 0.4.1 and dgeni-packages 0.10.1
Adds a new processor to identify dangling links
2014-10-06 16:55:52 +01:00
Pete Bacon Darwin 409ad62042 docs(CHANGELOG): remove reverted commit from 1.2.26
See https://github.com/angular/angular.js/pull/9079#issuecomment-57954483
2014-10-06 16:38:01 +01:00
Tobias Bosch 7cb01a80be fix($browser): don’t use history api when only the hash changes
Fixes a failing test on IE9 caused as a side effect
of 404b95fe30 being merged
before 0656484d3e.

The test should have been independent on the browser running it
and it is now.

Closes #9423
Closes #9424
2014-10-03 21:47:05 -07:00
Caitlin Potter feba0174db fix($compile): remove comment nodes from templates before asserting single root node
The compiler will no longer throw if a directive template contains comment nodes in addition to a
single root node. If a template contains less than 2 nodes, the nodes are unaltered.

BREAKING CHANGE:

If a template contains directives within comment nodes, and there is more than a single node in the
template, those comment nodes are removed. The impact of this breaking change is expected to be
quite low.

Closes #9212
Closes #9215
2014-10-03 21:41:13 -04:00
Igor Minar 7b6c1d08ac fix($http): honor application/json response header and parse json primitives
When server responds with Content-Type header set to application/json we now properly parse the response as JSON

Closes #2973
2014-10-03 16:25:11 -07:00
Tobias Bosch 858360b680 fix($browser): don’t use history api when only the hash changes
Fix jshint error
2014-10-03 16:23:25 -07:00
Shahar Talmi f7174169f4 fix(select): use $viewValue instead of $modelValue
Closes #8929
2014-10-03 16:12:52 -07:00
Tobias Bosch 0656484d3e fix($browser): don’t use history api when only the hash changes
IE10/11 have the following problem: When changing the url hash
via `history.pushState()` and then reverting the hash via direct
changes to `location.href` (or via a link) does not fire a
`hashchange` nor `popstate` event.

This commit changes the default behavior as follows:
Uses `location.href`/`location.replace` if the new url differs from
the previous url only in the hash fragment or the browser
does not support history API.
Use `history.pushState`/ `history.replaceState` otherwise.

Fixes #9143
Closes #9406
2014-10-03 16:04:51 -07:00
Tobias Bosch f3539f3cb5 fix($compile): use the correct namespace for transcluded svg elements
This fixes the case when a directive that uses `templateUrl`
is used somewhere in the children
of a transcluding directive like `ng-repeat`.

Fixes #9344
Related to #8808
Closes #9415
2014-10-03 15:58:06 -07:00
Chris Chua 404b95fe30 fix($browser): handle async href on url change in <=IE9
Closes #9235
2014-10-03 15:33:09 -07:00
Marcy Sutton d277641eec docs(guide/accessibility): Update documentation 2014-10-03 14:05:10 -07:00
Dominic Watson e7cf04bad3 docs(angular.element): css() method does not retrieve computed styles
The jQuery css() getter functionality utilises getComputedStyle() whereas
jqLite only retrieves what is declared inline on an element.

Closes #7599
2014-10-03 11:57:56 +01:00
Peter Bacon Darwin 607f016a0b fix(orderBy): sort by identity if no predicate is given
Closes #5847
Closes #4579
Closes #9403
2014-10-03 09:11:18 +01:00
Jeff Cross f2942447c1 chore(release): create script to undo a release for given number 2014-10-02 15:08:20 -07:00
Pawel Kozlowski b8c5b87119 fix($location): allow 0 in path() and hash() 2014-10-02 21:09:25 +01:00
jimmywarting 074a146d8b perf(ngBind): set textContent rather than using element.text()
"Speeds up chrome with ~10% firefox by ~5%"

We don't really see this result in benchmarks (https://www.dropbox.com/s/76wxqbvduade52s/big_table_benchmark_b1ee5396_vs_d580a954.zip?dl=0)
However, it's basically harmless.

Side effects:

Use strict equality check for `undefined` to replace with empty string. Most target browsers will output `undefined` rather than the empty
string if we don't do this. Previously, ngBindTemplate did not perform this check. However the change has been made to make behaviour
consistent across all target browsers (chrome does output the empty string).

Closes #9369
Closes #9396
2014-10-02 14:38:40 -04:00
Artem Tyurin 86c7d1221c fix(form): fix submit prevention
Do not prevent submit when action
attribute equals to an empty string.

Closes #3370
Closes #3776
2014-10-02 10:28:16 -07:00
Jeff Cross 8da08a1ebd docs(CHANGELOG.md): update 1.2.26 release name 2014-10-02 09:45:36 -07:00
Kent C. Dodds f7b2d85a2c test($compile): add test for alternative syntax to get controllers from ancestors
Because the regex that tests the `require` value will match more than just `^^?`,
it is important to test other common ways to specify a controller requirement
to ensure that a breaking change isn't introduced inadvertently. This adds a test
for `?^^`.

Closes #9389
Closes #9390
2014-10-02 11:51:43 -04:00
Peter Bacon Darwin 391d8c04da docs($compile): improve transclusion documentation
Closes #9352
2014-10-02 15:03:30 +01:00
Peter Bacon Darwin 11c76369aa docs(guide/accessibility): add sortOrder to get it in the right position in the guide 2014-10-02 13:13:32 +01:00
Jeff Cross dc5130c611 chore(docs): rearrange docs app search elements so close button is not first tab index 2014-10-01 23:05:36 -07:00
Jeff Cross 84912134d5 docs(changelog.md): update changelog for 1.3.0-rc.4 and 1.2.26 2014-10-01 21:24:01 -07:00
Peter Bacon Darwin 59cb9e8a77 chore(docs): show error 404 without partial failing
We can move the test back into the main describe as it no longer causes an
error message to be logged
2014-10-01 21:09:48 -07:00
Tobias Bosch 10644432ca fix(input): register builtin parsers/formatters before anyone else
Previously, builtin parsers/formatters for e.g. `input[date]`
or `input[number]` were added in the post linking phase to `ngModelController`,
which in most cases was after a custom formatter/parser was registered.

This commit registers builtin parsers/formatters already
in the pre linking phase. With that builtin
parsers run first, and builtin formatters run last.

Closes #9218
Closes #9358
2014-10-01 17:37:40 -07:00
Tobias Bosch a0bfdd0d60 fix(input): correctly handle invalid model values for input[date/time/…]
Similar to `input[number]` Angular will throw if the model value
for a `input[date]` is not a `Date` object.
For `Invalid Date`s (dates whose `getTime()` is `NaN`) `input[date]`
will render an empty string.

Closes #8949
Closes #9375
2014-10-01 16:12:05 -07:00
Igor Minar 3624e3800f fix(ngView): use animation promises ensure that only one leave animation occurs at a time
the tracking depended on a local flag variable, which was susceptible to corruption due to
race conditions.

using promises ensures that the previousLeaveAnimation is nulled out only if it hasn't been
canceled yet.

Closes #9355
Closes #7606
Closes #9374
2014-10-01 15:19:29 -07:00
Jason Bedard b1ee5386d5 perf(ngForm,ngModel): move initial addClass to the compile phase
Closes #8268
2014-09-30 21:45:41 -07:00
ltrillaud ab80cd9066 fix(compile): sanitize srcset attribute
Applies similar sanitization as is applie to img[src] to img[srcset],
while adapting to the different semantics and syntax of srcset.
2014-09-30 16:32:58 -07:00
Maxi Ferreira 8199f4dbde docs(guide/forms): improve example
When explaining ng-model-options, there's no print of `user.data` to show
the difference between the default behaviour and updateOn: 'blur'
2014-09-30 14:46:22 -07:00
Brian Feister 313d7956e4 docs(minerr/unpr): note that ctrls cant depend on other ctrls 2014-09-30 14:36:08 -07:00
Tobias Bosch b9479ee73b chore(ngCsp): add e2e tests
Also changes `connect:devserver` and `connect:testserver` to conditionally serve files with csp headers when the path contains `.csp` somewhere.

Closes #9136
Closes #9059
2014-09-30 14:10:19 -07:00
active-low 769a00dc86 docs(guide/concepts): improve readability 2014-09-30 12:56:45 -07:00
Adam Humphrey 6593c2371e docs(readme): fix formatting 2014-09-30 12:35:44 -07:00
thorn0 8b54524c07 docs($compile): fix a broken link 2014-09-30 12:33:01 -07:00
Justin Walsh 66bb5aa41c docs(guide/compiler): change span to block element in draggable example
The draggable example does not work as expected in Chrome (37.0.2062.124 m).
The span disappears when dragged beyond what appears to be a small area.
Changing the span to a block element (with a width of 65px) resolves this issue.
An alternative solution would be to change the span to a div.
2014-09-30 12:29:18 -07:00
thorn0 b186709003 docs($compile): add header to example 2014-09-30 11:40:48 -07:00
Caitlin Potter a27d827c22 fix($compile): get $$observe listeners array as own property
Prevent accidentally treating a builtin function from Object.prototype as the binding object, and thus
preventing the compiler from throwing when using attribute binding names which match a property of the
Object prototype.

Closes #9343
Closes #9345
2014-09-30 13:05:09 -04:00
Caitlin Potter a1648a76c0 docs(CHANGELOG.md): put <base> in codeblock
Prevent the tag from being processed (and not rendered). Thanks @davidlehn.

Closes #9331
2014-09-29 17:16:11 -04:00
Brian Ford 2bcd02dc1a fix(select): make ctrl.hasOption method consistent
Prior to this fix, options added to a select by ngOptions would not cause
`selectCtrl.hasOption` to return `true`

Closes #8761
2014-09-29 13:58:03 -07:00
Peter Bacon Darwin b0033a44bd chore(npm-shrinkwrap): update to dgeni-packages 0.10.0 2014-09-29 21:56:22 +01:00
Julie Ralph 76b755f3cb chore(e2e): bump protractor to version 1.3.1 2014-09-29 10:15:49 -07:00
Lucas Galfaso 6303c3dcf6 fix($compile): Resolve leak with asynchronous compilation
Stop an asynchronous compilation when this is performed on an
already destroyed scope

Closes #9199
Closes #9079
Closes #8504
Closes #9197
2014-09-29 12:47:21 +01:00
Lucas Galfaso cd2cfafcab refactor($scope): prevent multiple calls to listener on $destroy
Prevent isolated scopes from having listeners that get called
multiple times when on `$destroy`
2014-09-29 12:41:36 +01:00
Richard Littauer 86d33c5f9d docs(CONTRIBUTING.md): Added a not about type
It's important that we let people use the GitHub editing interface without being 100% strict about how to name the commit changes. Otherwise, it is basically a barrier to entry and highly discouraging for new people who may just be trying to fix a spelling error. Since it is possible for contributors to edit the commit message before merging it into master, for people who are new to the commit styling system, we should be lenient about minor infractions like forgetting to put docs: in front of a message. 

CF: https://github.com/angular-ui/bootstrap/pull/2635#issuecomment-57117579
2014-09-29 02:50:49 -07:00
Georgios Kalpakas eb935e6be0 test($http): fix typo in spec name
'applyAapply' -> 'applyAsync'.

Closes #9323
2014-09-28 20:33:05 -04:00
Jason Bedard b119251827 perf($rootScope): moving internal queues out of the Scope instances
Closes #9071
2014-09-27 08:19:15 -07:00
Jason Bedard 5572b40b15 refactor($parse): change 'this' to a $parse keyword instead of scope field
BREAKING CHANGE:
- $scope['this'] no longer exits on the $scope object
- $parse-ed expressions no longer allow chaining 'this' such as this['this'] or $parent['this']
- 'this' in $parse-ed expressions can no longer be overriden, if a variable named 'this' is put on the scope it must be accessed using this['this']

Closes #9105
2014-09-27 08:13:14 -07:00
Brian Iversen 4a6c7cf8ce docs(guide): update compiler guide with minor grammatical fixes
Minor changes to grammar. Changed sentence "But the declarative language
is also limited, since it does not allow you to teach the browser new syntax."
to now read "However, the declarative language is also limited, as it does not
allow you to teach the browser new syntax."
However is a less informal start to a sentence, and replacing "since"
correctly references extent/degree rather than comparison of time.
2014-09-26 17:16:23 -07:00
Victor Queiroz 27d12340d9 docs(guide): update directive guide to not imply ngView is part of core 2014-09-26 17:04:40 -07:00
Peter Bacon Darwin fb0c77f0b6 fix($compile): connect transclude scopes to their containing scope to prevent memory leaks
Transcluded scopes are now connected to the scope in which they are created
via their `$parent` property. This means that they will be automatically destroyed
when their "containing" scope is destroyed, without having to resort to listening
for a `$destroy` event on various DOM elements or other scopes.

Previously, transclude scope not only inherited prototypically from the scope from
which they were transcluded but they were also still owned by that "outer" scope.
This meant that there were scenarios where the "real" container scope/element was
destroyed but the transclude scope was not, leading to memory leaks.

The original strategy for dealing with this was to attach a `$destroy` event handler
to the DOM elements in the transcluded content, so that if the elements were removed
from the DOM then their associated transcluded scope would be destroyed.

This didn't work for transclude contents that didn't contain any elements - most
importantly in the case of the transclude content containing an element transclude
directive at its root, since the compiler swaps out this element for a comment
before a destroy handler could be attached.

BREAKING CHANGE:

`$transclude` functions no longer attach `$destroy` event handlers to the
transcluded content, and so the associated transclude scope will not automatically
be destroyed if you remove a transcluded element from the DOM using direct DOM
manipulation such as the jquery `remove()` method.

If you want to explicitly remove DOM elements inside your directive that have
been compiled, and so potentially contain child (and transcluded) scopes, then
it is your responsibility to get hold of the scope and destroy it at the same time.

The suggested approach is to create a new child scope of your own around any DOM
elements that you wish to manipulate in this way and destroy those scopes if you
remove their contents - any child scopes will then be destroyed and cleaned up
automatically.

Note that all the built-in directives that manipulate the DOM (ngIf, ngRepeat,
ngSwitch, etc) already follow this best practice, so if you only use these for
manipulating the DOM then you do not have to worry about this change.

Closes #9095
Closes #9281
2014-09-26 21:38:17 +01:00
Peter Bacon Darwin 6417a3e9eb feat(Scope): allow the parent of a new scope to be specified on creation
This enables us to place transclude scopes more accurately in the scope hierarchy.
2014-09-26 21:38:02 +01:00
Caitlin Potter 07e3abc7dd feat($compile): optionally get controllers from ancestors only
Implement option to strengthen require '^' operator, by adding another '^'.

When a second '^' is used, the controller will only search parent nodes for the
matching controller, and will throw or return null if not found, depending on
whether or not the requirement is optional.

Closes #4518
Closes #4540
Closes #8240
Closes #8511
2014-09-26 16:32:26 -04:00
Peter Bacon Darwin b9df121655 chore(docs): fix links to github
Closes https://github.com/angular/code.angularjs.org/issues/13
2014-09-26 20:51:55 +01:00
Brian Ford b5bb4a986a docs(guide/accessibility): explain ngAria 2014-09-26 12:02:02 -07:00
Michał Gołębiowski 8202c4dcea chore(Angular): drop support for Opera < 15
Closes #8589
2014-09-26 11:32:11 -07:00
Leonardo Zizzamia 2c8b464852 perf(benchmark): add ngBindOnce benchmarks to largetable-bp 2014-09-26 10:04:29 -07:00
Sandeep Panda a192c41ddc docs(guide/index): add book AngularJS: Novice to Ninja
I wrote a book on AngularJS (AngularJS: Novice to Ninja).

Closes #9293
2014-09-26 12:27:52 -04:00
Georgios Kalpakas a8fe2cc345 test(input): test that number validates with unspecified viewValue
Adds an additional test verifying that a number which is not required will validate successfully
when ngModelCtrl.$validate() is called. Before 92f05e5 landed, this would have failed because of
a parse error.

Closes #9193
2014-09-25 09:53:45 -04:00
Peter Bacon Darwin e522c25fd4 chore(docs): remove unused code 2014-09-25 05:43:20 +01:00
Peter Bacon Darwin 5dbc2d65f3 chore(docs): improve logo rendering performance 2014-09-25 05:32:12 +01:00
Peter Bacon Darwin 27300072d1 chore(protractor): annotate $animate to allow tests to run under strict-di 2014-09-25 05:32:12 +01:00
Peter Bacon Darwin 7ffc247d0f chore(docs): minify javascript 2014-09-25 05:32:12 +01:00
Peter Bacon Darwin 8ab673d430 chore(docs): ensure DI annotations are in place 2014-09-25 05:31:14 +01:00
Caitlin Potter b9e899c8b2 test(ngModel): rename test to better reflect what is being tested
I meant to do this in before 92f05e5a59 landed, sorry u_u
2014-09-24 18:04:37 -04:00
Caitlin Potter 92f05e5a59 fix(ngModel): do not parse undefined viewValue when validating
Previously, if a viewValue had not yet been set on the element, it could incorrectly produce a
parse error.

This change prevents the parsers from running if a view value has not yet been committed.

Closes #9106
Closes #9260
2014-09-24 18:00:20 -04:00
Peter Bacon Darwin e81ae1464d chore(docs): show error 404 without partial failing
We can move the test back into the main describe as it no longer causes an
error message to be logged
2014-09-24 07:32:04 +01:00
Jeff Cross de38899f74 docs(changelog): add release notes for 1.3.0-rc.3 2014-09-23 18:47:24 -07:00
Caitlin Potter 729c238e19 feat(input): support dynamic element validation
Interpolates the form and form control attribute name, so that dynamic form controls (such as those
rendered in an ngRepeat) will always have their expected interpolated name.

The control will be present in its parent form controller with the interpolated property name, and
this name can change when the interpolated value changes.

Closes #4791
Closes #1404
2014-09-23 16:03:53 -04:00
Jeff Cross dc3de7fb7a feat($location): add ability to opt-out of <base/> tag requirement in html5Mode
This feature allows disabling Angular's requirement of using a <base/> tag
when using location in html5Mode, for applications that do not require
using $location in html5Mode in IE9. To accomplish this, the $locationProvider.html5Mode 
method has been changed to accept a definition object which can optionally set a 
requireBase property to false, removing the requirement of a <base> tag being present
when html5Mode is enabled.

BREAKING CHANGE: The $location.html5Mode API has changed to allow enabling html5Mode by
    passing an object (as well as still supporting passing a boolean). Symmetrically, the
    method now returns an object instead of a boolean value.

    To migrate, follow the code example below:

    Before:

    var mode = $locationProvider.html5Mode();

    After:

    var mode = $locationProvider.html5Mode().enabled;

Fixes #8934
2014-09-23 11:34:24 -07:00
Peter Bacon Darwin ace40d5526 chore(docs): refactor the docs app search for better bootup time
This commit refactors how the search index is built. The docsSearch service
is now defined by a provider, which returns a different implementation of
the service depending upon whether the current browser supports WebWorkers
or now.

* **WebWorker supported**: The index is then built and stored in a new worker.
The service posts and receives messages to and from this worker to make
queries on the search index.

* **WebWorker no supported**: The index is built locally but with a 500ms
delay so that the initial page can render before the browser is blocked as
the index is built.

Also the way that the current app is identified has been modified so we can
slim down the js data files (pages-data.js) to again improve startup time.

Closes #9204
Closes #9203
2014-09-23 18:58:45 +01:00
Shahar Talmi fd8997551f feat(formController): add $setUntouched to propagate untouched state
Closes #9050
2014-09-23 13:48:39 -04:00
Brian Ford d8c8b2ebb7 chore(bower): add ngAria module to script 2014-09-22 15:27:00 -07:00
Andrew Delikat f5bb34ab4a docs(tutorial/step_05): fix typo 2014-09-22 14:52:20 -07:00
Shahar Talmi 4b83f6ca2c fix(ngModel): support milliseconds in time and datetime
Closes #8874
2014-09-22 14:50:08 -07:00
Rouven Weßling a591e8b8d3 perf(map): use Array.prototype.map
Replace helper functions with the native ES5 method
2014-09-22 14:09:48 -07:00
William Chen 6b05105c08 docs(triaging): fix formatting 2014-09-22 13:14:49 -07:00
Bocharsky Victor 728832ec85 docs(guide/$location): fix broken link 2014-09-22 13:12:27 -07:00
Christopher Rains f1a75a445c docs(tutorial/step_02): fix formatting 2014-09-22 13:07:19 -07:00
James Ferguson c59bee5d21 docs(readme): improve readability 2014-09-22 11:40:30 -07:00
Maarten Stolte 17ecf84b90 docs(ngAria): fix wording 2014-09-22 11:35:10 -07:00
Ariel Mashraki df8d9507aa docs(route): remove irrelevant note
Closes #9196
Closes #9200
2014-09-22 13:27:43 -04:00
krusty 6e7fbe77c9 docs(guide/directive): remove note about default restrict value
The text said a directive wouldn't work out of the box as an element, but the note immediatelly
below says that by default the directives restrict to elements or attributes.

11f5aee made the removed comments invalid.

Closes #9205
2014-09-21 20:59:20 -04:00
Jeff Cross 3686f45398 chore($http): disable flaky JSONP test
See #9185
2014-09-19 17:19:57 -07:00
Brian Ford ad28baaa6c refactor(ngAria): bind to ngModel rather than form types 2014-09-19 15:31:48 -07:00
Peter Bacon Darwin 8f9c4daca5 docs(limitTo): restore the missing * to make comment a jsdoc block 2014-09-19 22:53:48 +01:00
Peter Bacon Darwin f0c94ea292 docs(limitTo): exclude the e2e test that fails on safari
Safari and doesn't like the minus key to be sent to it via Protractor.
Commenting out rather than using xit so that it passes the build!
2014-09-19 22:45:14 +01:00
Peter Bacon Darwin 729129b461 docs(limitTo): exclude the e2e test that fails on safari
Safari and doesn't like the minus key to be sent to it via Protractor.
Commenting out rather than using xit so that it passes the build!
2014-09-19 22:42:48 +01:00
Peter Bacon Darwin bf2c55ea29 docs($aria): add basic missing docs for the $aria service
The individual service methods should be documented too.

cc: @arbus
2014-09-19 22:40:05 +01:00
Peter Bacon Darwin deafb5e545 docs(limitTo): exclude the e2e test that fails on safari
Safari and doesn't like the minus key to be sent to it via Protractor.
2014-09-19 22:35:51 +01:00
Ciro Nunes 0702aef7ee docs(guide/unit-testing): clarify the use of underscore notation
Closes #9024
2014-09-19 22:24:48 +01:00
Ciro Nunes f0ee335311 test(injector): allow service names with a single underscore
Closes #9024
2014-09-19 22:24:48 +01:00
Sekib Omazic 02169d4957 docs(angular.extend) actually only copies own enumerable properties
Closes #9007
2014-09-19 21:55:31 +01:00
Zahid Mahir 25d0eff3e6 docs(tutorial/step-3): correct slight grammar issue
Closes #8996
2014-09-19 21:36:08 +01:00
jimmywarting bd41bd594c docs(limitTo): fix input type in examples
Closes #8989
2014-09-19 21:24:04 +01:00
Georgios Kalpakas 373d7c95d9 docs(ngResource): fix error in one of the code examples
Closes #8948
Closes #9069
2014-09-19 19:36:43 +01:00
Sercan Eraslan 4c5c762378 docs(navigation): side navigation footer overlap problem fix
Closes #8923
2014-09-19 19:26:06 +01:00
Subra d1434c999a feat(ngAria): add an ngAria module to make a11y easier
Conditionally adds various aria attributes to the built in directives.
This module currently hooks into ng-show/hide, input, textarea and
button as a basic level of support for a11y.

Closes #5486 and #1600
2014-09-18 16:17:14 -07:00
Luke Schoen 8b8f6f5124 docs(guide/directive): fix grammar 2014-09-18 16:12:00 -07:00
Matt Kim 25082b3439 docs(misc/faq): fix typo 2014-09-18 16:09:56 -07:00
Greg Fedirko 27b3ea4d32 docs(guide/$location): improve readability 2014-09-18 16:08:27 -07:00
DeK ffc32b4e42 docs(guide/migration): fix typo 2014-09-18 16:02:26 -07:00
Luke Schoen 80b0909927 docs(tutorial): improve readability 2014-09-18 16:00:25 -07:00
Christopher Rains efbb365533 docs(tutorial): fix formatting
- proper case "jQuery" vs "JQuery"
- wrap ng-view in markdown code `ng-view`
2014-09-18 15:55:51 -07:00
Brian 38e0ab9bd8 docs(guide/filter): fix label in example 2014-09-18 15:53:56 -07:00
jeffavis 3c53b28cc2 docs(guide/bootstrap): fix missing ngController in example 2014-09-18 15:51:07 -07:00
Rahul Doshi 0ba864184b docs(guide): add angular-localization module to internationalization section
Closes #9158
2014-09-18 17:59:54 -04:00
Sebastian Müller 4f9dc44f88 refactor(ngMessages): remove unused $scope
Closes #9150
2014-09-18 16:04:47 -04:00
Georgios Kalpakas f3884df0a9 docs(ngController): Fix priority value mentioned in the docs
The `@priority 500` part was missing from the ngDoc comment, thus the docs mentioned a priority of 0
(instead of the correct 500).

Closes #9070
2014-09-18 11:43:17 -04:00
Peter Bacon Darwin f7a4a70c28 chore(npm-shrinkwrap): update to dgeni-packages v0.10.0-rc.6
Closes https://github.com/angular/dgeni-packages/pull/70
2014-09-17 19:11:24 +01:00
Jose Martinez 8428a0adef docs(error/$controller/noscp): fix example
Fix the "correct" example to have the proper syntax for creating the locals
object and provide a more explicit explanation as to how the scope object
should be provided.
2014-09-17 10:56:21 -07:00
143 changed files with 7276 additions and 2289 deletions
+335
View File
@@ -1,3 +1,338 @@
<a name="1.3.0-rc.5"></a>
# 1.3.0-rc.5 impossible-choreography (2014-10-08)
## Bug Fixes
- **$anchorScroll:** don't scroll to top when initializing and location hash is empty
([d5445c60](https://github.com/angular/angular.js/commit/d5445c601fafd6ecd38befeaa4c9ec7bb044127c),
[#8848](https://github.com/angular/angular.js/issues/8848), [#9393](https://github.com/angular/angular.js/issues/9393))
- **$animate:**
- ensure hidden elements with ngShow/ngHide stay hidden during animations
([39d0b368](https://github.com/angular/angular.js/commit/39d0b36826a077f7549a70d0cf3edebe90a10aaa),
[#9103](https://github.com/angular/angular.js/issues/9103), [#9493](https://github.com/angular/angular.js/issues/9493))
- permit class-based animations for leave operations if ngAnimateChildren is enabled
([df1a00b1](https://github.com/angular/angular.js/commit/df1a00b11ac2722f4da441837795985f12682030),
[#8092](https://github.com/angular/angular.js/issues/8092), [#9491](https://github.com/angular/angular.js/issues/9491))
- ensure that class-based animations only consider the most recent DOM operations
([c93924ed](https://github.com/angular/angular.js/commit/c93924ed275a62683b85c82f1c6c2e19d5662c9a),
[#8946](https://github.com/angular/angular.js/issues/8946), [#9458](https://github.com/angular/angular.js/issues/9458))
- abort class-based animations if the element is removed during digest
([613d0a32](https://github.com/angular/angular.js/commit/613d0a3212de8dc01c817ca8526e09c57978a621),
[#8796](https://github.com/angular/angular.js/issues/8796))
- clear the GCS cache even when no animation is detected
([cb85cbce](https://github.com/angular/angular.js/commit/cb85cbcec1c876db6062a0dc0bad80f842782194),
[#8813](https://github.com/angular/angular.js/issues/8813))
- **$browser:**
- Cache `location.href` only during page reload phase
([8ee1ba4b](https://github.com/angular/angular.js/commit/8ee1ba4b94d6fccff06d8781f7ed256c6ce664ff),
[#9235](https://github.com/angular/angular.js/issues/9235), [#9455](https://github.com/angular/angular.js/issues/9455))
- dont use the history API when only the hash changes
([7cb01a80](https://github.com/angular/angular.js/commit/7cb01a80beec669d8f6aae1dc211d2f0b7d4eac4),
[#9423](https://github.com/angular/angular.js/issues/9423), [#9424](https://github.com/angular/angular.js/issues/9424),
[858360b6](https://github.com/angular/angular.js/commit/858360b680a2bb5c19429c1be1c9506700cda476),
[0656484d](https://github.com/angular/angular.js/commit/0656484d3e709c5162570b0dd6473b0b6140e5b2),
[#9143](https://github.com/angular/angular.js/issues/9143), [#9406](https://github.com/angular/angular.js/issues/9406))
- handle async href on url change in <=IE9
([404b95fe](https://github.com/angular/angular.js/commit/404b95fe30a1bcd1313adafbd0018578d5b21d3d),
[#9235](https://github.com/angular/angular.js/issues/9235))
- **$compile:**
- handle the removal of an interpolated attribute
([a75546af](https://github.com/angular/angular.js/commit/a75546afdf41adab786eda30c258190cd4c5f1ae),
[#9236](https://github.com/angular/angular.js/issues/9236), [#9240](https://github.com/angular/angular.js/issues/9240))
- remove comment nodes from templates before asserting single root node
([feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
[#9212](https://github.com/angular/angular.js/issues/9212), [#9215](https://github.com/angular/angular.js/issues/9215))
- use the correct namespace for transcluded svg elements
([f3539f3c](https://github.com/angular/angular.js/commit/f3539f3cb5d9477f50f065c6a0ac7d6ca0a31092),
[#9344](https://github.com/angular/angular.js/issues/9344), [#9415](https://github.com/angular/angular.js/issues/9415))
- **$http:** honor application/json response header and parse json primitives
([7b6c1d08](https://github.com/angular/angular.js/commit/7b6c1d08aceba6704a40302f373400aed9ed0e0b),
[#2973](https://github.com/angular/angular.js/issues/2973))
- **$injector:** throw when factory $get method does not return a value
([0d3b69a5](https://github.com/angular/angular.js/commit/0d3b69a5f27b41745b504c7ffd8d72653bac1f85),
[#4575](https://github.com/angular/angular.js/issues/4575), [#9210](https://github.com/angular/angular.js/issues/9210))
- **$location:** allow `0` in `path()` and `hash()`
([b8c5b871](https://github.com/angular/angular.js/commit/b8c5b87119a06edb8e8d1cefad81ee8d1f64f070))
- **form:** fix submit prevention
([86c7d122](https://github.com/angular/angular.js/commit/86c7d1221c706993044583d51a0c61423fee5bcf),
[#3370](https://github.com/angular/angular.js/issues/3370), [#3776](https://github.com/angular/angular.js/issues/3776))
- **ngAnimate:** defer DOM operations for changing classes to postDigest
([667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
[#8234](https://github.com/angular/angular.js/issues/8234), [#9263](https://github.com/angular/angular.js/issues/9263))
- **orderBy:** sort by identity if no predicate is given
([607f016a](https://github.com/angular/angular.js/commit/607f016a0ba705ce40df0164360fb96a9d7f5912),
[#5847](https://github.com/angular/angular.js/issues/5847), [#4579](https://github.com/angular/angular.js/issues/4579), [#9403](https://github.com/angular/angular.js/issues/9403))
- **select:**
- throw for `selectAs` and `trackBy`
([30996f82](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d))
- use `$viewValue` instead of `$modelValue`
([f7174169](https://github.com/angular/angular.js/commit/f7174169f4f710d605f6a67f39f90a67a07d4cab),
[#8929](https://github.com/angular/angular.js/issues/8929))
## Features
- **$location:**
- add support for History API state handling ([6fd36dee](https://github.com/angular/angular.js/commit/6fd36deed954b338e48390862971d465148dc1f2),
[#9027](https://github.com/angular/angular.js/issues/9027))
- allow automatic rewriting of links to be disabled
([b3e09be5](https://github.com/angular/angular.js/commit/b3e09be58960b913fee3869bf36e7de3305bbe00),
[#5487](https://github.com/angular/angular.js/issues/5487))
- **$route:** ability to cancel $routeChangeStart event
([f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
[#5581](https://github.com/angular/angular.js/issues/5581), [#5714](https://github.com/angular/angular.js/issues/5714), [#9502](https://github.com/angular/angular.js/issues/9502))
## Performance Improvements
- **$animate:**
- access DOM less in resolveElementClasses
([22358cf9](https://github.com/angular/angular.js/commit/22358cf9c703d67f3cf9eb4899404b09578a5fad))
- don't join classes before it's necessary in resolveElementClasses
([003c44ec](https://github.com/angular/angular.js/commit/003c44eceee54c3398b0d2971fd97a512d7f7cec))
- **ngBind:** set textContent rather than using element.text()
([074a146d](https://github.com/angular/angular.js/commit/074a146d8b1ee7c93bf6d5892448a5c2a0143a28),
[#9369](https://github.com/angular/angular.js/issues/9369), [#9396](https://github.com/angular/angular.js/issues/9396))
## Breaking Changes
- **$compile:** due to [feba0174](https://github.com/angular/angular.js/commit/feba0174db0f8f929273beb8b90691734a9292e2),
If a template contains directives within comment nodes, and there is more than a single node in the
template, those comment nodes are removed. The impact of this breaking change is expected to be
quite low.
Closes #9212
Closes #9215
- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
The `$animate` CSS class API will always defer changes until the end of the next digest. This allows ngAnimate
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
many. This prevents jank in browsers such as IE, and is generally a good thing.
If you find that your classes are not being immediately applied, be sure to invoke `$digest()`.
Closes #8234
Closes #9263
- **$select:** due to [30996f8](https://github.com/angular/angular.js/commit/30996f82afa03cd11771b3267e9367ecf9af6e6d)
`ngOptions` will now throw an error when the comprehension expressions contains both a `select as`
and `track by` expression.
These expressions are fundamentally incompatible because it is not possible to reliably and
consistently determine the parent object of a model, since `select as` can assign any child of a
`value` as the model value.
Prior to refactorings in this release, neither of these expressions worked correctly independently,
and did not work at all when combined.
See #6564
- **$route:** due to [f4ff11b0](https://github.com/angular/angular.js/commit/f4ff11b01e6a5f9a9eb25a38d327dfaadbd7c80c),
Order of events has changed.
Previously: `$locationChangeStart` -> `$locationChangeSuccess`
-> `$routeChangeStart` -> `$routeChangeSuccess`
Now: `$locationChangeStart` -> `$routeChangeStart`
-> `$locationChangeSuccess` -> -> `$routeChangeSuccess`
Fixes #5581
Closes #5714
Closes #9502- **ngAnimate:** due to [667183a8](https://github.com/angular/angular.js/commit/667183a8c79d6ffce571a2be78c05dc76503b222),
The $animate class API will always defer changes until the end of the next digest. This allows ngAnimate
to coalesce class changes which occur over a short period of time into 1 or 2 DOM writes, rather than
many. This prevents jank in browsers such as IE, and is generally a good thing.
If you're finding that your classes are not being immediately applied, be sure to invoke $digest().
Closes #8234
Closes #9263
<a name="1.3.0-rc.4"></a>
# 1.3.0-rc.4 unicorn-hydrafication (2014-10-01)
## Bug Fixes
- **$compile:**
- get $$observe listeners array as own property
([a27d827c](https://github.com/angular/angular.js/commit/a27d827c22b0b6b3ba6b7495cf4fc338c6934b37),
[#9343](https://github.com/angular/angular.js/issues/9343), [#9345](https://github.com/angular/angular.js/issues/9345))
- Resolve leak with asynchronous compilation
([6303c3dc](https://github.com/angular/angular.js/commit/6303c3dcf64685458fc84aa12289f5c9d57f4e47),
[#9199](https://github.com/angular/angular.js/issues/9199), [#9079](https://github.com/angular/angular.js/issues/9079), [#8504](https://github.com/angular/angular.js/issues/8504), [#9197](https://github.com/angular/angular.js/issues/9197))
- connect transclude scopes to their containing scope to prevent memory leaks
([fb0c77f0](https://github.com/angular/angular.js/commit/fb0c77f0b66ed757a56af13f81b943419fdcbd7f),
[#9095](https://github.com/angular/angular.js/issues/9095), [#9281](https://github.com/angular/angular.js/issues/9281))
- sanitize srcset attribute
([ab80cd90](https://github.com/angular/angular.js/commit/ab80cd90661396dbb1c94c5f4dd2d11ee8f6b6af))
- **input:**
- register builtin parsers/formatters before anyone else
([10644432](https://github.com/angular/angular.js/commit/10644432ca9d5da69ce790a8d9e691640f333711),
[#9218](https://github.com/angular/angular.js/issues/9218), [#9358](https://github.com/angular/angular.js/issues/9358))
- correctly handle invalid model values for `input[date/time/…]`
([a0bfdd0d](https://github.com/angular/angular.js/commit/a0bfdd0d60882125f614a91c321f12f730735e7b),
[#8949](https://github.com/angular/angular.js/issues/8949), [#9375](https://github.com/angular/angular.js/issues/9375))
- **ngModel:** do not parse undefined viewValue when validating
([92f05e5a](https://github.com/angular/angular.js/commit/92f05e5a5900713301e64373d7b7daa45a88278b),
[#9106](https://github.com/angular/angular.js/issues/9106), [#9260](https://github.com/angular/angular.js/issues/9260))
- **ngView:** use animation promises ensure that only one leave animation occurs at a time
([3624e380](https://github.com/angular/angular.js/commit/3624e3800fb3ccd2e9ea361a763e20131fd42c29),
[#9355](https://github.com/angular/angular.js/issues/9355), [#7606](https://github.com/angular/angular.js/issues/7606), [#9374](https://github.com/angular/angular.js/issues/9374))
- **select:** make ctrl.hasOption method consistent
([2bcd02dc](https://github.com/angular/angular.js/commit/2bcd02dc1a6b28b357d47c83be3bed5c9a38417c),
[#8761](https://github.com/angular/angular.js/issues/8761))
## Features
- **$compile:** optionally get controllers from ancestors only
([07e3abc7](https://github.com/angular/angular.js/commit/07e3abc7dda872adc3fb25cb3e133f86f494b35d),
[#4518](https://github.com/angular/angular.js/issues/4518), [#4540](https://github.com/angular/angular.js/issues/4540), [#8240](https://github.com/angular/angular.js/issues/8240), [#8511](https://github.com/angular/angular.js/issues/8511))
- **Scope:** allow the parent of a new scope to be specified on creation
([6417a3e9](https://github.com/angular/angular.js/commit/6417a3e9eb7ab0011cefada8db855aa929a64ff8))
## Performance Improvements
- **$rootScope:** moving internal queues out of the Scope instances
([b1192518](https://github.com/angular/angular.js/commit/b119251827cea670051198e1b48af7ee0c9f2a1b),
[#9071](https://github.com/angular/angular.js/issues/9071))
- **benchmark:** add ngBindOnce benchmarks to largetable-bp
([2c8b4648](https://github.com/angular/angular.js/commit/2c8b4648526acf5c2645de8408a6d9ace2144b5f))
- **ngForm,ngModel:** move initial addClass to the compile phase
([b1ee5386](https://github.com/angular/angular.js/commit/b1ee5386d584f208bce6d3b613afdb3bae9df76a),
[#8268](https://github.com/angular/angular.js/issues/8268))
## Breaking Changes
- **$compile:** due to [fb0c77f0](https://github.com/angular/angular.js/commit/fb0c77f0b66ed757a56af13f81b943419fdcbd7f),
`$transclude` functions no longer attach `$destroy` event handlers to the
transcluded content, and so the associated transclude scope will not automatically
be destroyed if you remove a transcluded element from the DOM using direct DOM
manipulation such as the jquery `remove()` method.
If you want to explicitly remove DOM elements inside your directive that have
been compiled, and so potentially contain child (and transcluded) scopes, then
it is your responsibility to get hold of the scope and destroy it at the same time.
The suggested approach is to create a new child scope of your own around any DOM
elements that you wish to manipulate in this way and destroy those scopes if you
remove their contents - any child scopes will then be destroyed and cleaned up
automatically.
Note that all the built-in directives that manipulate the DOM (ngIf, ngRepeat,
ngSwitch, etc) already follow this best practice, so if you only use these for
manipulating the DOM then you do not have to worry about this change.
Closes #9095
Closes #9281
- **$parse:** due to [5572b40b](https://github.com/angular/angular.js/commit/5572b40b15ed06969c8e0e92866c5afd088484b4),
- $scope['this'] no longer exits on the $scope object
- $parse-ed expressions no longer allow chaining 'this' such as this['this'] or $parent['this']
- 'this' in $parse-ed expressions can no longer be overriden, if a variable named 'this' is put on the scope it must be accessed using this['this']
Closes #9105
- **input:** due to [1eda1836](https://github.com/angular/angular.js/commit/1eda18365a348c9597aafba9d195d345e4f13d1e),
(Note: this change landed in 1.3.0-rc.3, but was not considered a breaking change at the time).
For text based inputs (text, email, url), the `$viewValue` will now always be converted to a string,
regardless of what type the value is on the model.
To migrate, any code or expressions that expect the `$viewValue` to be anything other than string
should be updated to expect a string.
- **input:** due to a0bfdd0d60882125f614a91c321f12f730735e7b (see #8949),
Similar to `input[number]` Angular will now throw if the model value
for a `input[date]` is not a `Date` object. Previously, Angular only
showed an empty string instead.
Angular does not set validation errors on the `<input>` in this case
as those errors are shown to the user, but the erroneous state was
caused by incorrect application logic and not by the user.
<a name="1.2.26"></a>
# 1.2.26 captivating-disinterest (2014-10-01)
## Bug Fixes
- **select:** make ctrl.hasOption method consistent
([11d2242d](https://github.com/angular/angular.js/commit/11d2242df65b2ade0dabe366a0c42963b6d37df5),
[#8761](https://github.com/angular/angular.js/issues/8761))
<a name="1.3.0-rc.3"></a>
# 1.3.0-rc.3 aggressive-pacification (2014-09-23)
## Bug Fixes
- **ngModel:** support milliseconds in time and datetime
([4b83f6ca](https://github.com/angular/angular.js/commit/4b83f6ca2c15bd65fe2b3894a02c04f9967fbff4),
[#8874](https://github.com/angular/angular.js/issues/8874))
## Features
- **$location:** add ability to opt-out of `<base>` tag requirement in html5Mode
([dc3de7fb](https://github.com/angular/angular.js/commit/dc3de7fb7a14c38b5c3dc7decfafb0b51d422dd1),
[#8934](https://github.com/angular/angular.js/issues/8934))
- **formController:** add $setUntouched to propagate untouched state
([fd899755](https://github.com/angular/angular.js/commit/fd8997551f9ed4431f5e99d61f637139485076b9),
[#9050](https://github.com/angular/angular.js/issues/9050))
- **input:** support dynamic element validation
([729c238e](https://github.com/angular/angular.js/commit/729c238e19ab27deff01448d79342ea53721bfed),
[#4791](https://github.com/angular/angular.js/issues/4791), [#1404](https://github.com/angular/angular.js/issues/1404))
- **ngAria:** add an ngAria module to make a11y easier
([d1434c99](https://github.com/angular/angular.js/commit/d1434c999a66c6bb915ee1a8b091e497d288d940),
[#5486](https://github.com/angular/angular.js/issues/5486))
## Performance Improvements
- **map:** use Array.prototype.map
([a591e8b8](https://github.com/angular/angular.js/commit/a591e8b8d302efefd67bf0d5c4bad300a5f3aded))
## Breaking Changes
- **$location:** due to [dc3de7fb](https://github.com/angular/angular.js/commit/dc3de7fb7a14c38b5c3dc7decfafb0b51d422dd1),
The $location.html5Mode API has changed to allow enabling html5Mode by
passing an object (as well as still supporting passing a boolean). Symmetrically, the
method now returns an object instead of a boolean value.
To migrate, follow the code example below:
Before:
var mode = $locationProvider.html5Mode();
After:
var mode = $locationProvider.html5Mode().enabled;
Fixes #8934
<a name="1.2.25"></a>
# 1.2.25 hypnotic-gesticulation (2014-09-16)
+4 -4
View File
@@ -54,7 +54,7 @@ For large fixes, please build and test the documentation before submitting the P
accidentally introduced any layout or formatting issues. You should also make sure that your commit message
is labeled "docs:" and follows the **Git Commit Guidelines** outlined below.
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly.
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly. When naming the commit, it is advised to still label it according to the commit guidelines below, by starting the commit message with **docs** and referencing the filename. Since this is not obvious and some changes are made on the fly, this is not strictly necessary and we will understand if this isn't done the first few times.
## <a name="submit"></a> Submission Guidelines
@@ -66,13 +66,13 @@ Help us to maximize the effort we can spend fixing issues and adding new
features, by not reporting duplicate issues. Providing the following information will increase the
chances of your issue being dealt with quickly:
* **Overview of the issue** - if an error is being thrown a non-minified stack trace helps
* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
* **Motivation for or Use Case** - explain why this is a bug for you
* **Angular Version(s)** - is it a regression?
* **Browsers and Operating System** - is this a problem with all browsers or only IE8?
* **Reproduce the error** - provide a live example (using [Plunker][plunker] or
* **Reproduce the Error** - provide a live example (using [Plunker][plunker] or
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
* **Related issues** - has a similar issue been reported before?
* **Related Issues** - has a similar issue been reported before?
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
causing the problem (line of code or commit)
+11 -3
View File
@@ -47,8 +47,7 @@ module.exports = function(grunt) {
keepalive: true,
middleware: function(connect, options){
return [
//uncomment to enable CSP
// util.csp(),
util.conditionalCsp(),
util.rewrite(),
connect.favicon('images/favicon.ico'),
connect.static(options.base),
@@ -74,6 +73,7 @@ module.exports = function(grunt) {
next();
},
util.conditionalCsp(),
connect.favicon('images/favicon.ico'),
connect.static(options.base)
];
@@ -153,6 +153,9 @@ module.exports = function(grunt) {
},
ngTouch: {
files: { src: 'src/ngTouch/**/*.js' },
},
ngAria: {
files: {src: 'src/ngAria/**/*.js'},
}
},
@@ -220,6 +223,10 @@ module.exports = function(grunt) {
dest: 'build/angular-cookies.js',
src: util.wrap(files['angularModules']['ngCookies'], 'module')
},
aria: {
dest: 'build/angular-aria.js',
src: util.wrap(files['angularModules']['ngAria'], 'module')
},
"promises-aplus-adapter": {
dest:'tmp/promises-aplus-adapter++.js',
src:['src/ng/q.js','lib/promises-aplus/promises-aplus-test-adapter.js']
@@ -236,7 +243,8 @@ module.exports = function(grunt) {
touch: 'build/angular-touch.js',
resource: 'build/angular-resource.js',
route: 'build/angular-route.js',
sanitize: 'build/angular-sanitize.js'
sanitize: 'build/angular-sanitize.js',
aria: 'build/angular-aria.js'
},
+5 -4
View File
@@ -6,10 +6,11 @@ use good old HTML (or HAML, Jade and friends!) as your template language and let
syntax to express your applications components clearly and succinctly. It automatically
synchronizes data from your UI (view) with your JavaScript objects (model) through 2-way data
binding. To help you structure your application better and make it easy to test, AngularJS teaches
the browser how to do dependency injection and inversion of control. Oh yeah and it also helps with
server-side communication, taming async callbacks with promises and deferreds; and makes client-side
navigation and deeplinking with hashbang urls or HTML5 pushState a piece of cake. The best of all:
it makes development fun!
the browser how to do dependency injection and inversion of control.
Oh yeah and it helps with server-side communication, taming async callbacks with promises and
deferreds. It also makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
piece of cake. The best of all: it makes development fun!
* Web site: http://angularjs.org
* Tutorial: http://docs.angularjs.org/tutorial
-3
View File
@@ -26,7 +26,6 @@ This process based on the idea of minimizing user pain
* You can triage older issues as well
* Triage to your heart's content
1. Assign yourself: Pick an issue that is not assigned to anyone and assign it to you
1. Understandable? - verify if the description of the request is clear.
* If not, [close it][] according to the instructions below and go to the last step.
1. Duplicate?
@@ -36,7 +35,6 @@ This process based on the idea of minimizing user pain
* Label `Type: Bug`
* Reproducible? - Steps to reproduce the bug are clear. If they are not, ask for a clarification. If there's no reply after a week, [close it][].
* Reproducible on master? - <http://code.angularjs.org/snapshot/>
1. Non bugs:
* Label `Type: Feature`, `Type: Chore`, or `Type: Perf`
* Belongs in core? Often new features should be implemented as a third-party module rather than an addition to the core.
@@ -59,7 +57,6 @@ This process based on the idea of minimizing user pain
* In rare cases, it's ok to have multiple components.
1. Label `PRs plz!` - These issues are good targets for PRs from the open source community. Apply to issues where the problem and solution are well defined in the comments, and it's not too complex.
1. Label `origin: google` for issues from Google
1. Assign a milestone:
* Backlog - triaged fixes and features, should be the default choice
* Current 1.x.y milestone (e.g. 1.3.0-beta-2) - regressions and urgent bugs only
+9 -3
View File
@@ -108,6 +108,9 @@ var angularFiles = {
'src/ngTouch/directive/ngClick.js',
'src/ngTouch/directive/ngSwipe.js'
],
'ngAria': [
'src/ngAria/aria.js'
]
},
'angularScenario': [
@@ -141,7 +144,8 @@ var angularFiles = {
'test/ngRoute/**/*.js',
'test/ngSanitize/**/*.js',
'test/ngMock/*.js',
'test/ngTouch/**/*.js'
'test/ngTouch/**/*.js',
'test/ngAria/*.js'
],
'karma': [
@@ -175,7 +179,8 @@ var angularFiles = {
'test/ngRoute/**/*.js',
'test/ngResource/*.js',
'test/ngSanitize/**/*.js',
'test/ngTouch/**/*.js'
'test/ngTouch/**/*.js',
'test/ngAria/*.js'
],
'karmaJquery': [
@@ -203,7 +208,8 @@ angularFiles['angularSrcModules'] = [].concat(
angularFiles['angularModules']['ngRoute'],
angularFiles['angularModules']['ngSanitize'],
angularFiles['angularModules']['ngMock'],
angularFiles['angularModules']['ngTouch']
angularFiles['angularModules']['ngTouch'],
angularFiles['angularModules']['ngAria']
);
if (exports) {
+21 -10
View File
@@ -8,15 +8,16 @@
Large table rendered with AngularJS
</p>
<div>none: <input type=radio ng-model="benchmarkType" value="none"></div>
<div>baseline binding: <input type=radio ng-model="benchmarkType" value="baselineBinding"></div>
<div>baseline interpolation: <input type=radio ng-model="benchmarkType" value="baselineInterpolation"></div>
<div>ngBind: <input type=radio ng-model="benchmarkType" value="ngBind"></div>
<div>interpolation: <input type=radio ng-model="benchmarkType" value="interpolation"></div>
<div>ngBind + fnInvocation: <input type=radio ng-model="benchmarkType" value="ngBindFn"></div>
<div>interpolation + fnInvocation: <input type=radio ng-model="benchmarkType" value="interpolationFn"></div>
<div>ngBind + filter: <input type=radio ng-model="benchmarkType" value="ngBindFilter"></div>
<div>interpolation + filter: <input type=radio ng-model="benchmarkType" value="interpolationFilter"></div>
<div>none: <input type="radio" ng-model="benchmarkType" value="none"></div>
<div>baseline binding: <input type="radio" ng-model="benchmarkType" value="baselineBinding"></div>
<div>baseline interpolation: <input type="radio" ng-model="benchmarkType" value="baselineInterpolation"></div>
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
<div>ngBind + filter: <input type="radio" ng-model="benchmarkType" value="ngBindFilter"></div>
<div>interpolation + filter: <input type="radio" ng-model="benchmarkType" value="interpolationFilter"></div>
<ng-switch on="benchmarkType">
<baseline-binding-table ng-switch-when="baselineBinding">
@@ -26,7 +27,17 @@
<div ng-switch-when="ngBind">
<h2>baseline binding</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row"><span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|</span>
<span ng-repeat="column in row">
<span ng-bind="column.i"></span>:<span ng-bind="column.j"></span>|
</span>
</div>
</div>
<div ng-switch-when="ngBindOnce">
<h2>baseline binding once</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in ::row">
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
</span>
</div>
</div>
<div ng-switch-when="interpolation">
+1 -1
View File
@@ -2,7 +2,7 @@
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],
.ng-cloak, .x-ng-cloak,
.ng-hide:not(.ng-animate) {
.ng-hide:not(.ng-hide-animate) {
display: none !important;
}
+4 -4
View File
@@ -316,10 +316,10 @@ iframe.example {
}
.search-results-group.col-group-api { width:30%; }
.search-results-group.col-group-guide { width:30%; }
.search-results-group.col-group-tutorial { width:25%; }
.search-results-group.col-group-guide,
.search-results-group.col-group-tutorial { width:20%; }
.search-results-group.col-group-misc,
.search-results-group.col-group-error { float:right; clear:both; width:15% }
.search-results-group.col-group-error { width:15%; float: right; }
.search-results-group.col-group-api .search-result {
@@ -391,7 +391,6 @@ iframe.example {
position:fixed;
top:120px;
bottom:0;
padding-bottom:120px;
overflow:auto;
}
@@ -412,6 +411,7 @@ iframe.example {
.main-body-grid .side-navigation {
position:relative;
padding-bottom:120px;
}
.main-body-grid .side-navigation.ng-hide {
@@ -1,284 +0,0 @@
'use strict';
var directive = {};
var service = { value: {} };
var DEPENDENCIES = {
'angular.js': 'http://code.angularjs.org/' + angular.version.full + '/angular.min.js',
'angular-resource.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-resource.min.js',
'angular-route.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-route.min.js',
'angular-animate.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-animate.min.js',
'angular-sanitize.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-sanitize.min.js',
'angular-cookies.js': 'http://code.angularjs.org/' + angular.version.full + '/angular-cookies.min.js'
};
function escape(text) {
return text.
replace(/\&/g, '&amp;').
replace(/\</g, '&lt;').
replace(/\>/g, '&gt;').
replace(/"/g, '&quot;');
}
/**
* http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
* http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
*/
function setHtmlIe8SafeWay(element, html) {
var newElement = angular.element('<pre>' + html + '</pre>');
element.empty();
element.append(newElement.contents());
return element;
}
directive.jsFiddle = function(getEmbeddedTemplate, escape, script) {
return {
terminal: true,
link: function(scope, element, attr) {
var name = '',
stylesheet = '<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">\n',
fields = {
html: '',
css: '',
js: ''
};
angular.forEach(attr.jsFiddle.split(' '), function(file, index) {
var fileType = file.split('.')[1];
if (fileType == 'html') {
if (index == 0) {
fields[fileType] +=
'<div ng-app' + (attr.module ? '="' + attr.module + '"' : '') + '>\n' +
getEmbeddedTemplate(file, 2);
} else {
fields[fileType] += '\n\n\n <!-- CACHE FILE: ' + file + ' -->\n' +
' <script type="text/ng-template" id="' + file + '">\n' +
getEmbeddedTemplate(file, 4) +
' </script>\n';
}
} else {
fields[fileType] += getEmbeddedTemplate(file) + '\n';
}
});
fields.html += '</div>\n';
setHtmlIe8SafeWay(element,
'<form class="jsfiddle" method="post" action="http://jsfiddle.net/api/post/library/pure/" target="_blank">' +
hiddenField('title', 'AngularJS Example: ' + name) +
hiddenField('css', '</style> <!-- Ugly Hack due to jsFiddle issue: http://goo.gl/BUfGZ --> \n' +
stylesheet +
script.angular +
(attr.resource ? script.resource : '') +
'<style>\n' +
fields.css) +
hiddenField('html', fields.html) +
hiddenField('js', fields.js) +
'<button class="btn btn-primary"><i class="icon-white icon-pencil"></i> Edit Me</button>' +
'</form>');
function hiddenField(name, value) {
return '<input type="hidden" name="' + name + '" value="' + escape(value) + '">';
}
}
}
};
directive.ngSetText = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
return {
restrict: 'CA',
priority: 10,
compile: function(element, attr) {
setHtmlIe8SafeWay(element, escape(getEmbeddedTemplate(attr.ngSetText)));
}
}
}]
directive.ngHtmlWrap = ['reindentCode', 'templateMerge', function(reindentCode, templateMerge) {
return {
compile: function(element, attr) {
var properties = {
head: '',
module: '',
body: element.text()
},
html = "<!doctype html>\n<html ng-app{{module}}>\n <head>\n{{head:4}} </head>\n <body>\n{{body:4}} </body>\n</html>";
angular.forEach((attr.ngHtmlWrap || '').split(' '), function(dep) {
if (!dep) return;
dep = DEPENDENCIES[dep] || dep;
var ext = dep.split(/\./).pop();
if (ext == 'css') {
properties.head += '<link rel="stylesheet" href="' + dep + '" type="text/css">\n';
} else if(ext == 'js') {
properties.head += '<script src="' + dep + '"></script>\n';
} else {
properties.module = '="' + dep + '"';
}
});
setHtmlIe8SafeWay(element, escape(templateMerge(html, properties)));
}
}
}];
directive.ngSetHtml = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
return {
restrict: 'CA',
priority: 10,
compile: function(element, attr) {
setHtmlIe8SafeWay(element, getEmbeddedTemplate(attr.ngSetHtml));
}
}
}];
directive.ngEvalJavascript = ['getEmbeddedTemplate', function(getEmbeddedTemplate) {
return {
compile: function (element, attr) {
var fileNames = attr.ngEvalJavascript.split(' ');
angular.forEach(fileNames, function(fileName) {
var script = getEmbeddedTemplate(fileName);
try {
if (window.execScript) { // IE
window.execScript(script || '""'); // IE complains when evaling empty string
} else {
window.eval(script + '//@ sourceURL=' + fileName);
}
} catch (e) {
if (window.console) {
window.console.log(script, '\n', e);
} else {
window.alert(e);
}
}
});
}
};
}];
directive.ngEmbedApp = ['$templateCache', '$browser', '$rootScope', '$location', '$sniffer', '$animate',
function($templateCache, $browser, docsRootScope, $location, $sniffer, $animate) {
return {
terminal: true,
link: function(scope, element, attrs) {
var modules = ['ngAnimate'],
embedRootScope,
deregisterEmbedRootScope;
modules.push(['$provide', function($provide) {
$provide.value('$templateCache', $templateCache);
$provide.value('$anchorScroll', angular.noop);
$provide.value('$browser', $browser);
$provide.value('$sniffer', $sniffer);
$provide.value('$animate', $animate);
$provide.provider('$location', function() {
this.$get = ['$rootScope', function($rootScope) {
docsRootScope.$on('$locationChangeSuccess', function(event, oldUrl, newUrl) {
$rootScope.$broadcast('$locationChangeSuccess', oldUrl, newUrl);
});
return $location;
}];
this.html5Mode = angular.noop;
});
$provide.decorator('$rootScope', ['$delegate', function($delegate) {
embedRootScope = $delegate;
// Since we are teleporting the $animate service, which relies on the $$postDigestQueue
// we need the embedded scope to use the same $$postDigestQueue as the outer scope
embedRootScope.$$postDigestQueue = docsRootScope.$$postDigestQueue;
deregisterEmbedRootScope = docsRootScope.$watch(function embedRootScopeDigestWatch() {
embedRootScope.$digest();
});
return embedRootScope;
}]);
}]);
if (attrs.ngEmbedApp) modules.push(attrs.ngEmbedApp);
element.on('click', function(event) {
if (event.target.attributes.getNamedItem('ng-click')) {
event.preventDefault();
}
});
element.on('$destroy', function() {
deregisterEmbedRootScope();
embedRootScope.$destroy();
});
element.data('$injector', null);
angular.bootstrap(element, modules);
}
};
}];
service.reindentCode = function() {
return function (text, spaces) {
if (!text) return text;
var lines = text.split(/\r?\n/);
var prefix = ' '.substr(0, spaces || 0);
var i;
// remove any leading blank lines
while (lines.length && lines[0].match(/^\s*$/)) lines.shift();
// remove any trailing blank lines
while (lines.length && lines[lines.length - 1].match(/^\s*$/)) lines.pop();
var minIndent = 999;
for (i = 0; i < lines.length; i++) {
var line = lines[0];
var reindentCode = line.match(/^\s*/)[0];
if (reindentCode !== line && reindentCode.length < minIndent) {
minIndent = reindentCode.length;
}
}
for (i = 0; i < lines.length; i++) {
lines[i] = prefix + lines[i].substring(minIndent);
}
lines.push('');
return lines.join('\n');
}
};
service.templateMerge = ['reindentCode', function(indentCode) {
return function(template, properties) {
return template.replace(/\{\{(\w+)(?:\:(\d+))?\}\}/g, function(_, key, indent) {
var value = properties[key];
if (indent) {
value = indentCode(value, indent);
}
return value == undefined ? '' : value;
});
};
}];
service.getEmbeddedTemplate = ['reindentCode', function(reindentCode) {
return function (id) {
var element = document.getElementById(id);
if (!element) {
return null;
}
return reindentCode(angular.element(element).html(), 0);
}
}];
angular.module('bootstrapPrettify', []).directive(directive).factory(service);
+44
View File
@@ -0,0 +1,44 @@
"use strict";
/* jshint browser: true */
/* global importScripts, onmessage: true, postMessage, lunr */
// Load up the lunr library
importScripts('../components/lunr.js-0.4.2/lunr.min.js');
// Create the lunr index - the docs should be an array of object, each object containing
// the path and search terms for a page
var index = lunr(function() {
this.ref('path');
this.field('titleWords', {boost: 50});
this.field('members', { boost: 40});
this.field('keywords', { boost : 20 });
});
// Retrieve the searchData which contains the information about each page to be indexed
var searchData = {};
var searchDataRequest = new XMLHttpRequest();
searchDataRequest.onload = function() {
// Store the pages data to be used in mapping query results back to pages
searchData = JSON.parse(this.responseText);
// Add search terms from each page to the search index
searchData.forEach(function(page) {
index.add(page);
});
postMessage({ e: 'index-ready' });
};
searchDataRequest.open('GET', 'search-data.json');
searchDataRequest.send();
// The worker receives a message everytime the web app wants to query the index
onmessage = function(oEvent) {
var q = oEvent.data.q;
var hits = index.search(q);
var results = [];
// Only return the array of paths to pages
hits.forEach(function(hit) {
results.push(hit.ref);
});
// The results of the query are sent back to the web app via a new message
postMessage({ e: 'query-ready', q: q, d: results });
};
+2 -2
View File
@@ -76,11 +76,11 @@ describe('docs.angularjs.org', function () {
expect(element(by.css('.minerr-errmsg')).getText()).toEqual("Argument 'Missing' is not a function, got undefined");
});
it("should display an error if the page does not exist", function() {
browser.get('index-debug.html#!/api/does/not/exist');
expect(element(by.css('h1')).getText()).toBe('Oops!');
});
});
});
});
+3 -3
View File
@@ -6,6 +6,7 @@ angular.module('docsApp', [
'DocsController',
'versionsData',
'pagesData',
'navData',
'directives',
'errors',
'examples',
@@ -13,11 +14,10 @@ angular.module('docsApp', [
'tutorials',
'versions',
'bootstrap',
'bootstrapPrettify',
'ui.bootstrap.dropdown'
])
.config(function($locationProvider) {
.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
});
}]);
+9 -74
View File
@@ -6,31 +6,10 @@ angular.module('DocsController', [])
function($scope, $rootScope, $location, $window, $cookies, openPlunkr,
NG_PAGES, NG_NAVIGATION, NG_VERSION) {
$scope.openPlunkr = openPlunkr;
$scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version;
$scope.fold = function(url) {
if(url) {
$scope.docs_fold = '/notes/' + url;
if(/\/build/.test($window.location.href)) {
$scope.docs_fold = '/build/docs' + $scope.docs_fold;
}
window.scrollTo(0,0);
}
else {
$scope.docs_fold = null;
}
};
var OFFLINE_COOKIE_NAME = 'ng-offline',
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
/**********************************
Publish methods
***********************************/
$scope.navClass = function(navItem) {
return {
active: navItem.href && this.currentPage && this.currentPage.path,
@@ -38,55 +17,22 @@ angular.module('DocsController', [])
};
};
$scope.afterPartialLoaded = function() {
$scope.$on('$includeContentLoaded', function() {
var pagePath = $scope.currentPage ? $scope.currentPage.path : $location.path();
$window._gaq.push(['_trackPageview', pagePath]);
};
/** stores a cookie that is used by apache to decide which manifest ot send */
$scope.enableOffline = function() {
//The cookie will be good for one year!
var date = new Date();
date.setTime(date.getTime()+(365*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
var value = angular.version.full;
document.cookie = OFFLINE_COOKIE_NAME + "="+value+expires+"; path=" + $location.path;
//force the page to reload so server can serve new manifest file
window.location.reload(true);
};
/**********************************
Watches
***********************************/
});
$scope.$watch(function docsPathWatch() {return $location.path(); }, function docsPathWatchAction(path) {
var currentPage = $scope.currentPage = NG_PAGES[path];
if ( !currentPage && path.charAt(0)==='/' ) {
// Strip off leading slash
path = path.substr(1);
}
currentPage = $scope.currentPage = NG_PAGES[path];
if ( !currentPage && path.charAt(path.length-1) === '/' && path.length > 1 ) {
// Strip off trailing slash
path = path.substr(0, path.length-1);
}
currentPage = $scope.currentPage = NG_PAGES[path];
if ( !currentPage && /\/index$/.test(path) ) {
// Strip off index from the end
path = path.substr(0, path.length - 6);
}
path = path.replace(/^\/?(.+?)(\/index)?\/?$/, '$1');
currentPage = $scope.currentPage = NG_PAGES[path];
if ( currentPage ) {
$scope.currentArea = currentPage && NG_NAVIGATION[currentPage.area];
$scope.partialPath = 'partials/' + path + '.html';
$scope.currentArea = NG_NAVIGATION[currentPage.area];
var pathParts = currentPage.path.split('/');
var breadcrumb = $scope.breadcrumb = [];
var breadcrumbPath = '';
@@ -98,6 +44,7 @@ angular.module('DocsController', [])
} else {
$scope.currentArea = NG_NAVIGATION['api'];
$scope.breadcrumb = [];
$scope.partialPath = 'Error404.html';
}
});
@@ -107,24 +54,12 @@ angular.module('DocsController', [])
$scope.versionNumber = angular.version.full;
$scope.version = angular.version.full + " " + angular.version.codeName;
$scope.subpage = false;
$scope.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
$scope.futurePartialTitle = null;
$scope.loading = 0;
$scope.$cookies = $cookies;
$cookies.platformPreference = $cookies.platformPreference || 'gitUnix';
var INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
if (!$location.path() || INDEX_PATH.test($location.path())) {
$location.path('/api').replace();
}
// bind escape to hash reset callback
angular.element(window).on('keydown', function(e) {
if (e.keyCode === 27) {
$scope.$apply(function() {
$scope.subpage = false;
});
}
});
}]);
-24
View File
@@ -1,24 +0,0 @@
angular.module('docsApp.navigationService', [])
.factory('navigationService', function($window) {
var service = {
currentPage: null,
currentVersion: null,
changePage: function(newPage) {
},
changeVersion: function(newVersion) {
//TODO =========
// var currentPagePath = '';
// // preserve URL path when switching between doc versions
// if (angular.isObject($rootScope.currentPage) && $rootScope.currentPage.section && $rootScope.currentPage.id) {
// currentPagePath = '/' + $rootScope.currentPage.section + '/' + $rootScope.currentPage.id;
// }
// $window.location = version.url + currentPagePath;
}
};
});
+123 -64
View File
@@ -10,22 +10,35 @@ angular.module('search', [])
$scope.search = function(q) {
var MIN_SEARCH_LENGTH = 2;
if(q.length >= MIN_SEARCH_LENGTH) {
var results = docsSearch(q);
var totalAreas = 0;
for(var i in results) {
++totalAreas;
}
if(totalAreas > 0) {
$scope.colClassName = 'cols-' + totalAreas;
}
$scope.hasResults = totalAreas > 0;
$scope.results = results;
docsSearch(q).then(function(hits) {
var results = {};
angular.forEach(hits, function(hit) {
var area = hit.area;
var limit = (area == 'api') ? 40 : 14;
results[area] = results[area] || [];
if(results[area].length < limit) {
results[area].push(hit);
}
});
var totalAreas = 0;
for(var i in results) {
++totalAreas;
}
if(totalAreas > 0) {
$scope.colClassName = 'cols-' + totalAreas;
}
$scope.hasResults = totalAreas > 0;
$scope.results = results;
});
}
else {
clearResults();
}
if(!$scope.$$phase) $scope.$apply();
};
$scope.submit = function() {
var result;
for(var i in $scope.results) {
@@ -39,78 +52,124 @@ angular.module('search', [])
$scope.hideResults();
}
};
$scope.hideResults = function() {
clearResults();
$scope.q = '';
};
}])
.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch', function($scope, $location, docsSearch) {
$scope.results = docsSearch($location.path().split(/[\/\.:]/).pop());
.controller('Error404SearchCtrl', ['$scope', '$location', 'docsSearch',
function($scope, $location, docsSearch) {
docsSearch($location.path().split(/[\/\.:]/).pop()).then(function(results) {
$scope.results = {};
angular.forEach(results, function(result) {
var area = $scope.results[result.area] || [];
area.push(result);
$scope.results[result.area] = area;
});
});
}])
.factory('lunrSearch', function() {
return function(properties) {
if (window.RUNNING_IN_NG_TEST_RUNNER) return null;
var engine = lunr(properties);
return {
store : function(values) {
engine.add(values);
},
search : function(q) {
return engine.search(q);
}
};
};
})
.provider('docsSearch', function() {
.factory('docsSearch', ['$rootScope','lunrSearch', 'NG_PAGES',
function($rootScope, lunrSearch, NG_PAGES) {
if (window.RUNNING_IN_NG_TEST_RUNNER) {
return null;
}
// This version of the service builds the index in the current thread,
// which blocks rendering and other browser activities.
// It should only be used where the browser does not support WebWorkers
function localSearchFactory($http, $timeout, NG_PAGES) {
var index = lunrSearch(function() {
this.ref('id');
this.field('title', {boost: 50});
this.field('members', { boost: 40});
this.field('keywords', { boost : 20 });
});
console.log('Using Local Search Index');
angular.forEach(NG_PAGES, function(page, key) {
if(page.searchTerms) {
index.store({
id : key,
title : page.searchTerms.titleWords,
keywords : page.searchTerms.keywords,
members : page.searchTerms.members
// Create the lunr index
var index = lunr(function() {
this.ref('path');
this.field('titleWords', {boost: 50});
this.field('members', { boost: 40});
this.field('keywords', { boost : 20 });
});
// Delay building the index by loading the data asynchronously
var indexReadyPromise = $http.get('js/search-data.json').then(function(response) {
var searchData = response.data;
// Delay building the index for 500ms to allow the page to render
return $timeout(function() {
// load the page data into the index
angular.forEach(searchData, function(page) {
index.add(page);
});
}, 500);
});
// The actual service is a function that takes a query string and
// returns a promise to the search results
// (In this case we just resolve the promise immediately as it is not
// inherently an async process)
return function(q) {
return indexReadyPromise.then(function() {
var hits = index.search(q);
var results = [];
angular.forEach(hits, function(hit) {
results.push(NG_PAGES[hit.ref]);
});
return results;
});
};
});
}
localSearchFactory.$inject = ['$http', '$timeout', 'NG_PAGES'];
return function(q) {
var results = {
api : [],
tutorial : [],
guide : [],
error : [],
misc : []
// This version of the service builds the index in a WebWorker,
// which does not block rendering and other browser activities.
// It should only be used where the browser does support WebWorkers
function webWorkerSearchFactory($q, $rootScope, NG_PAGES) {
console.log('Using WebWorker Search Index')
var searchIndex = $q.defer();
var results;
var worker = new Worker('js/search-worker.js');
// The worker will send us a message in two situations:
// - when the index has been built, ready to run a query
// - when it has completed a search query and the results are available
worker.onmessage = function(oEvent) {
$rootScope.$apply(function() {
switch(oEvent.data.e) {
case 'index-ready':
searchIndex.resolve();
break;
case 'query-ready':
var pages = oEvent.data.d.map(function(path) {
return NG_PAGES[path];
});
results.resolve(pages);
break;
}
});
};
angular.forEach(index.search(q), function(result) {
var key = result.ref;
var item = NG_PAGES[key];
var area = item.area;
item.path = key;
var limit = area == 'api' ? 40 : 14;
if(results[area].length < limit) {
results[area].push(item);
}
});
return results;
// The actual service is a function that takes a query string and
// returns a promise to the search results
return function(q) {
// We only run the query once the index is ready
return searchIndex.promise.then(function() {
results = $q.defer();
worker.postMessage({ q: q });
return results.promise;
});
};
}
webWorkerSearchFactory.$inject = ['$q', '$rootScope', 'NG_PAGES'];
return {
$get: window.Worker ? webWorkerSearchFactory : localSearchFactory
};
}])
})
.directive('focused', function($timeout) {
return function(scope, element, attrs) {
+15 -16
View File
@@ -1,6 +1,6 @@
angular.module('tutorials', [])
.directive('docTutorialNav', function(templateMerge) {
.directive('docTutorialNav', function() {
var pages = [
'',
'step_00', 'step_01', 'step_02', 'step_03', 'step_04',
@@ -8,23 +8,22 @@ angular.module('tutorials', [])
'step_10', 'step_11', 'step_12', 'the_end'
];
return {
compile: function(element, attrs) {
var seq = 1 * attrs.docTutorialNav,
props = {
seq: seq,
prev: pages[seq],
next: pages[2 + seq],
diffLo: seq ? (seq - 1): '0~1',
diffHi: seq
};
scope: {},
template:
'<a ng-href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
'<a ng-href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
'<a ng-href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>',
link: function(scope, element, attrs) {
var seq = 1 * attrs.docTutorialNav;
scope.seq = seq;
scope.prev = pages[seq];
scope.next = pages[2 + seq];
scope.diffLo = seq ? (seq - 1): '0~1';
scope.diffHi = seq;
element.addClass('btn-group');
element.addClass('tutorial-nav');
element.append(templateMerge(
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
'<a href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
'<a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>', props));
}
};
})
@@ -47,4 +46,4 @@ angular.module('tutorials', [])
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}">GitHub</a>\n' +
'</p>'
};
});
});
-2
View File
@@ -1,5 +1,3 @@
"use strict";
angular.module('versions', [])
.controller('DocsVersionsCtrl', ['$scope', '$location', '$window', 'NG_VERSIONS', function($scope, $location, $window, NG_VERSIONS) {
+2 -2
View File
@@ -19,7 +19,7 @@ describe("DocsController", function() {
it("should update the Google Analytics with currentPage path if currentPage exists", inject(function($window) {
$window._gaq = [];
$scope.currentPage = { path: 'a/b/c' };
$scope.afterPartialLoaded();
$scope.$broadcast('$includeContentLoaded');
expect($window._gaq.pop()).toEqual(['_trackPageview', 'a/b/c']);
}));
@@ -27,7 +27,7 @@ describe("DocsController", function() {
it("should update the Google Analytics with $location.path if currentPage is missing", inject(function($window, $location) {
$window._gaq = [];
spyOn($location, 'path').andReturn('x/y/z');
$scope.afterPartialLoaded();
$scope.$broadcast('$includeContentLoaded');
expect($window._gaq.pop()).toEqual(['_trackPageview', 'x/y/z']);
}));
});
+18 -5
View File
@@ -92,10 +92,7 @@ module.exports = new Package('angularjs', [
}
return docPath;
},
getOutputPath: function(doc) {
return 'partials/' + doc.path +
( doc.fileInfo.baseName === 'index' ? '/index.html' : '.html');
}
outputPathTemplate: 'partials/${path}.html'
});
computePathsProcessor.pathTemplates.push({
@@ -106,10 +103,20 @@ module.exports = new Package('angularjs', [
computePathsProcessor.pathTemplates.push({
docTypes: ['indexPage'],
getPath: function() {},
pathTemplate: '.',
outputPathTemplate: '${id}.html'
});
computePathsProcessor.pathTemplates.push({
docTypes: ['module' ],
pathTemplate: '${area}/${name}',
outputPathTemplate: 'partials/${area}/${name}.html'
});
computePathsProcessor.pathTemplates.push({
docTypes: ['componentGroup' ],
pathTemplate: '${area}/${moduleName}/${groupType}',
outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}.html'
});
computeIdsProcessor.idTemplates.push({
docTypes: ['overview', 'tutorial', 'e2e-test', 'indexPage'],
@@ -124,6 +131,12 @@ module.exports = new Package('angularjs', [
});
})
.config(function(checkAnchorLinksProcessor) {
checkAnchorLinksProcessor.base = '/';
// We are only interested in docs that have an area (i.e. they are pages)
checkAnchorLinksProcessor.checkDoc = function(doc) { return doc.area; };
})
.config(function(
generateIndexPagesProcessor,
+40 -26
View File
@@ -147,24 +147,18 @@ module.exports = function generatePagesDataProcessor(log) {
};
return {
$runAfter: ['paths-computed'],
$runAfter: ['paths-computed', 'generateKeywordsProcessor'],
$runBefore: ['rendering-docs'],
$process: function(docs) {
_(docs)
.filter(function(doc) { return doc.area === 'api' && doc.docType === 'module'; })
.forEach(function(doc) { if ( !doc.path ) {
log.warn('Missing path property for ', doc.id);
}})
.map(function(doc) { return _.pick(doc, ['id', 'module', 'docType', 'area']); })
.tap(function(docs) {
log.debug(docs);
// We are only interested in docs that are in an area
var pages = _.filter(docs, function(doc) {
return doc.area;
});
// We are only interested in docs that are in an area and are not landing pages
var navPages = _.filter(docs, function(page) {
return page.area && page.docType != 'componentGroup';
// We are only interested in pages that are not landing pages
var navPages = _.filter(pages, function(page) {
return page.docType != 'componentGroup';
});
// Generate an object collection of pages that is grouped by area e.g.
@@ -198,28 +192,48 @@ module.exports = function generatePagesDataProcessor(log) {
area.navGroups = navGroupMapper(pages, area);
});
docs.push({
docType: 'nav-data',
id: 'nav-data',
template: 'nav-data.template.js',
outputPath: 'js/nav-data.js',
areas: areas
});
var searchData = _(pages)
.filter(function(page) {
return page.searchTerms;
})
.map(function(page) {
return _.extend({ path: page.path }, page.searchTerms);
})
.value();
docs.push({
docType: 'json-doc',
id: 'search-data-json',
template: 'json-doc.template.json',
outputPath: 'js/search-data.json',
data: searchData
});
// Extract a list of basic page information for mapping paths to partials and for client side searching
var pages = _(docs)
var pageData = _(docs)
.map(function(doc) {
var page = _.pick(doc, [
'docType', 'id', 'name', 'area', 'outputPath', 'path', 'searchTerms'
]);
return page;
return _.pick(doc, ['name', 'area', 'path']);
})
.indexBy('path')
.value();
var docData = {
docs.push({
docType: 'pages-data',
id: 'pages-data',
template: 'pages-data.template.js',
outputPath: 'js/pages-data.js',
areas: areas,
pages: pages
};
docs.push(docData);
pages: pageData
});
}
}
};
};
+1 -1
View File
@@ -19,13 +19,13 @@ module.exports = function debugDeployment(getVersion) {
'../angular-animate.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'js/angular-bootstrap/bootstrap.js',
'js/angular-bootstrap/bootstrap-prettify.js',
'js/angular-bootstrap/dropdown-toggle.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/versions-data.js',
'js/pages-data.js',
'js/nav-data.js',
'js/docs.js'
],
stylesheets: [
+4 -4
View File
@@ -18,15 +18,15 @@ module.exports = function defaultDeployment(getVersion) {
'../angular-touch.min.js',
'../angular-animate.min.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'js/angular-bootstrap/bootstrap.js',
'js/angular-bootstrap/bootstrap-prettify.js',
'js/angular-bootstrap/dropdown-toggle.js',
'js/angular-bootstrap/bootstrap.min.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/versions-data.js',
'js/pages-data.js',
'js/docs.js'
'js/nav-data.js',
'js/docs.min.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
+4 -4
View File
@@ -22,15 +22,15 @@ module.exports = function jqueryDeployment(getVersion) {
'../angular-touch.min.js',
'../angular-animate.min.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'js/angular-bootstrap/bootstrap.js',
'js/angular-bootstrap/bootstrap-prettify.js',
'js/angular-bootstrap/dropdown-toggle.js',
'js/angular-bootstrap/bootstrap.min.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/versions-data.js',
'js/pages-data.js',
'js/docs.js'
'js/nav-data.js',
'js/docs.min.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
@@ -21,15 +21,15 @@ module.exports = function productionDeployment(getVersion) {
cdnUrl + '/angular-touch.min.js',
cdnUrl + '/angular-animate.min.js',
'components/marked-' + getVersion('marked', 'node_modules', 'package.json') + '/lib/marked.js',
'js/angular-bootstrap/bootstrap.js',
'js/angular-bootstrap/bootstrap-prettify.js',
'js/angular-bootstrap/dropdown-toggle.js',
'js/angular-bootstrap/bootstrap.min.js',
'js/angular-bootstrap/dropdown-toggle.min.js',
'components/lunr.js-' + getVersion('lunr.js') + '/lunr.min.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/prettify.js',
'components/google-code-prettify-' + getVersion('google-code-prettify') + '/src/lang-css.js',
'js/versions-data.js',
'js/pages-data.js',
'js/docs.js'
'js/nav-data.js',
'js/docs.min.js'
],
stylesheets: [
'components/bootstrap-' + getVersion('bootstrap') + '/css/bootstrap.min.css',
-1
View File
@@ -1,6 +1,5 @@
"use strict";
var gruntUtils = require('../../../lib/grunt/utils');
var versionInfo = require('../../../lib/versions/version-info');
/**
+6 -15
View File
@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" ng-app="docsApp" ng-controller="DocsController">
<html lang="en" ng-app="docsApp" ng-strict-di ng-controller="DocsController">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
@@ -56,15 +56,6 @@
}
})();
// force page reload when new update is available
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
window.applicationCache.swapCache();
window.location.reload();
}
}, false);
// GA asynchronous tracker
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-8594346-3']);
@@ -85,7 +76,7 @@
<div class="row">
<div class="col-md-9 header-branding">
<a class="brand navbar-brand" href="http://angularjs.org">
<img class="logo" src="img/angularjs-for-header-only.svg">
<img width="117" height="30" class="logo" ng-src="img/angularjs-for-header-only.svg">
</a>
<ul class="nav navbar-nav">
<li class="divider-vertical"></li>
@@ -155,9 +146,6 @@
</div>
<div class="search-results-container" ng-show="hasResults">
<div class="container">
<a href="" ng-click="hideResults()" class="search-close">
<span class="glyphicon glyphicon-remove search-close-icon"></span> Close
</a>
<div class="search-results-frame">
<div ng-repeat="(key, value) in results" class="search-results-group" ng-class="colClassName + ' col-group-' + key">
<h4 class="search-results-group-heading">{{ key }}</h4>
@@ -168,6 +156,9 @@
</div>
</div>
</div>
<a href="" ng-click="hideResults()" class="search-close">
<span class="glyphicon glyphicon-remove search-close-icon"></span> Close
</a>
</div>
</div>
</section>
@@ -219,7 +210,7 @@
</div>
<div class="grid-right">
<div id="loading" ng-show="loading">Loading...</div>
<div ng-hide="loading" ng-include="currentPage.outputPath || 'Error404.html'" onload="afterPartialLoaded()" autoscroll></div>
<div ng-hide="loading" ng-include="partialPath" autoscroll></div>
</div>
</div>
</section>
@@ -0,0 +1 @@
{$ doc.data | json $}
@@ -0,0 +1,3 @@
// Meta data used by the AngularJS docs app
angular.module('navData', [])
.value('NG_NAVIGATION', {$ doc.areas | json $});
+1 -2
View File
@@ -1,4 +1,3 @@
// Meta data used by the AngularJS docs app
angular.module('pagesData', [])
.value('NG_PAGES', {$ doc.pages | json $})
.value('NG_NAVIGATION', {$ doc.areas | json $});
.value('NG_PAGES', {$ doc.pages | json $});
+2 -2
View File
@@ -12,10 +12,10 @@ $controller(MyController);
$controller(MyController, {scope: newScope});
```
To fix the example above please provide a scope to the $controller call:
To fix the example above please provide a scope (using the `$scope` property in the locals object) to the $controller call:
```
$controller(MyController, {$scope, newScope});
$controller(MyController, {$scope: newScope});
```
Please consult the {@link ng.$controller $controller} service api docs to learn more.
@@ -1,10 +0,0 @@
@ngdoc error
@name $httpBackend:noxhr
@fullName Unsupported XHR
@description
This error occurs in browsers that do not support XmlHttpRequest. AngularJS
supports Safari, Chrome, Firefox, Opera, IE8 and higher, and mobile browsers
(Android, Chrome Mobile, iOS Safari). To avoid this error, use an officially
supported browser.
+1 -1
View File
@@ -7,5 +7,5 @@ This error occurs when a module fails to load due to some exception. The error
message above should provide additional context.
In AngularJS `1.2.0` and later, `ngRoute` has been moved to its own module.
If you are getting this error after upgrading to `1.2.x`, be sure that you've
If you are getting this error after upgrading to `1.2.x` or later, be sure that you've
installed {@link ngRoute `ngRoute`}.
+33
View File
@@ -0,0 +1,33 @@
@ngdoc error
@name $injector:undef
@fullName Undefined Value
@description
This error results from registering a factory which does not return a value (or whose return value is undefined).
The following is an example of a factory which will throw this error upon injection:
```js
angular.module("badModule", []).
factory("badFactory", function() {
doLotsOfThings();
butDontReturnAValue();
});
```
In order to prevent the error, return a value of some sort, such as an object which exposes an API for working
with the injected object.
```js
angular.module("goodModule", []).
factory("goodFactory", function() {
doLotsOfThings();
butDontReturnAValue();
return {
doTheThing: function methodThatDoesAThing() {
}
};
});
```
+15 -1
View File
@@ -54,4 +54,18 @@ angular.module('myModule')
.directive('myDirective', ['myCoolService', function (myCoolService) {
// This directive definition does not throw unknown provider.
}]);
```
```
Attempting to inject one controller into another will also throw an `Unknown provider` error:
```
angular.module('myModule', [])
.controller('MyFirstController', function() { /* ... */ });
.controller('MySecondController', ['MyFirstController', function(MyFirstController) {
// This controller throws an unknown provider error because
// MyFirstController cannot be injected.
}]);
```
Use the `$controller` service if you want to instantiate controllers yourself.
+13 -1
View File
@@ -4,7 +4,19 @@
@description
If you configure {@link ng.$location `$location`} to use
{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [`<base href="">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag.
{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [`<base href="">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag or configure
`$locationProvider` to not require a base tag by passing a definition object with
`requireBase:false` to `$locationProvider.html5Mode()`:
```javascript
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
```
Note that removing the requirement for a <base> tag will have adverse side effects when resolving
relative paths with `$location` in IE9.
The base URL is then used to resolve all relative URLs throughout the application regardless of the
entry point into the app.
@@ -0,0 +1,8 @@
@ngdoc error
@name $location:nostate
@fullName History API state support is available only in HTML5 mode and only in browsers supporting HTML5 History API
@description
This error occurs when the {@link ng.$location#state $location.state} method is used when {@link ng.$locationProvider#html5Mode $locationProvider.html5Mode} is not turned on or the browser used doesn't support the HTML5 History API (for example, IE9 or Android 2.3).
To avoid this error, either drop support for those older browsers or avoid using this method.
+11
View File
@@ -0,0 +1,11 @@
@ngdoc error
@name ngModel:datefmt
@fullName Model is not a date object
@description
All date-related inputs like `<input type="date">` require the model to be a `Date` object.
If the model is something else, this error will be thrown.
Angular does not set validation errors on the `<input>` in this case
as those errors are shown to the user, but the erroneous state was
caused by incorrect application logic and not by the user.
@@ -0,0 +1,30 @@
@ngdoc error
@name ngOptions:trkslct
@fullName Comprehension expression cannot contain both `select as` and `track by` expressions.
@description
This error occurs when 'ngOptions' is passed a comprehension expression that contains both a
`select as` expression and a `track by` expression. These two expressions are fundamentally
incompatible.
* Example of bad expression: `<select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">`
`values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}]`,
`$scope.selected = {name: 'aSubItem'};`
* track by is always applied to `value`, with purpose to preserve the selection,
(to `item` in this case)
* To calculate whether an item is selected, `ngOptions` does the following:
1. apply `track by` to the values in the array:
In the example: [1,2]
2. apply `track by` to the already selected value in `ngModel`:
In the example: this is not possible, as `track by` refers to `item.id`, but the selected
value from `ngModel` is `{name: aSubItem}`.
Here's an example of how to make this example work by using `track by` without `select as`:
```
<select ng-model="selected" ng-options="item.label for item in values track by item.id">
```
Note: This would store the whole `item` as the model to `scope.selected` instead of `item.subItem`.
For more information on valid expression syntax, see 'ngOptions' in {@link ng.directive:select select} directive docs.
+12 -9
View File
@@ -91,10 +91,11 @@ To configure the `$location` service, retrieve the
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
- **html5Mode(mode)**: {boolean}<br />
`true` - see HTML5 mode<br />
`false` - see Hashbang mode<br />
default: `false`
- **html5Mode(mode)**: {boolean|Object}<br />
`true` or `enabled:true` - see HTML5 mode<br />
`false` or `enabled:false` - see Hashbang mode<br />
`requireBase:true` - see Relative links<br />
default: `enabled:false`
- **hashPrefix(prefix)**: {string}<br />
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
@@ -164,7 +165,7 @@ encoded.
`$location` service has two configuration modes which control the format of the URL in the browser
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
HTML5 [History API](http://www.w3.org/TR/html5/history.html). Applications use the same API in
HTML5 [History API](http://www.w3.org/TR/html5/introduction.html#history-0). Applications use the same API in
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
facilitate the browser URL change and history management.
@@ -245,7 +246,7 @@ it('should show example', inject(
## HTML5 mode
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
through the HTML5 history API, which allows for use of regular URL path and search segments,
through the HTML5 history API. This allows for use of regular URL path and search segments,
instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the
`$location` service will fall back to using the hashbang URLs automatically. This frees you from
having to worry about whether the browser displaying your app supports the history API or not; the
@@ -328,9 +329,11 @@ reload to the original link.
### Relative links
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url base in
the head of your main html file (`<base href="/my-base">`). With that, relative urls will
always be resolved to this base url, event if the initial url of the document was different.
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url
base in the head of your main html file (`<base href="/my-base">`) unless `html5Mode.requireBase` is
set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
that, relative urls will always be resolved to this base url, event if the initial url of the
document was different.
There is one exception: Links that only contain a hash fragment (e.g. `<a href="#target">`)
will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling
+60
View File
@@ -0,0 +1,60 @@
@ngdoc overview
@name Accessibility
@sortOrder 530
@description
# Accessibility with ngAria
You can use the `ngAria` module to have common ARIA attributes automatically applied when you
use certain directives. To enable `ngAria`, just require the module into your application and
the code will hook into your ng-show/ng-hide, input, textarea, button, select and
ng-required directives and add the appropriate ARIA states and properties.
Currently, the following attributes are implemented:
* aria-hidden
* aria-checked
* aria-disabled
* aria-required
* aria-invalid
* aria-multiline
* aria-valuenow
* aria-valuemin
* aria-valuemax
* tabindex
You can disable individual attributes by using the `{@link ngAria.$ariaProvider#config config}` method.
###Example
```js
angular.module('myApp', ['ngAria'])...
```
Elements using `ng-model` with `required` or `ngRequired` directives will automatically have
`aria-required` attributes with the proper corresponding values.
```html
<material-input ng-model="val" required>
```
Becomes:
```html
<material-input ng-model="val" required aria-required="true">
```
ngAria is just a starting point. You'll have to manually choose how to implement some
accessibility features.
For instance, you may want to add `ng-keypress` bindings alongside `ng-click` to make keyboard
navigation easier.
## Additional Resources
Accessibility best practices that apply to web apps in general also apply to Angular.
* [WebAim](http://webaim.org/)
* [Using WAI-ARIA in HTML](http://www.w3.org/TR/2014/WD-aria-in-html-20140626/)
* [Apps For All: Coding Accessible Web Applications](https://shop.smashingmagazine.com/apps-for-all-coding-accessible-web-applications.html)
+1 -1
View File
@@ -6,7 +6,7 @@
# Animations
AngularJS 1.2 provides animation hooks for common directives such as `ngRepeat`, `ngSwitch`, and `ngView`, as well as custom directives
AngularJS 1.3 provides animation hooks for common directives such as `ngRepeat`, `ngSwitch`, and `ngView`, as well as custom directives
via the `$animate` service. These animation hooks are set in place to trigger animations during the life cycle of various directives and when
triggered, will attempt to perform a CSS Transition, CSS Keyframe Animation or a JavaScript callback Animation (depending on if an animation is
placed on the given directive). Animations can be placed using vanilla CSS by following the naming conventions set in place by AngularJS
+3 -1
View File
@@ -87,7 +87,9 @@ Here is an example of manually initializing Angular:
<!doctype html>
<html>
<body>
Hello {{greetMe}}!
<div ng-controller="MyController">
Hello {{greetMe}}!
</div>
<script src="http://code.angularjs.org/snapshot/angular.js"></script>
<script>
+5 -3
View File
@@ -25,8 +25,8 @@ browser how the window size needs to be divided in half so that the center is fo
center needs to be aligned with the text's center. Simply add an `align="center"` attribute to any
element to achieve the desired behavior. Such is the power of declarative language.
But the declarative language is also limited, since it does not allow you to teach the browser new
syntax. For example there is no easy way to get the browser to align the text at 1/3 the position
However, the declarative language is also limited, as it does not allow you to teach the browser new
syntax. For example, there is no easy way to get the browser to align the text at 1/3 the position
instead of 1/2. What is needed is a way to teach the browser new HTML syntax.
Angular comes pre-bundled with common directives which are useful for building any app. We also
@@ -85,7 +85,9 @@ Here is a directive which makes any element draggable. Notice the `draggable` at
position: 'relative',
border: '1px solid red',
backgroundColor: 'lightgrey',
cursor: 'pointer'
cursor: 'pointer',
display: 'block',
width: '65px'
});
element.on('mousedown', function(event) {
// Prevent default dragging of selected content
+2 -2
View File
@@ -67,8 +67,8 @@ element that adds extra behavior to the element. The {@link ng.directive:ngModel
stores/updates the value of the input field into/from a variable.
<div class="alert alert-info">
**Custom directives to access the DOM**: In Angular, the only place where an application touches the DOM is
within directives. This is good as artifacts that access the DOM are hard to test.
**Custom directives to access the DOM**: In Angular, the only place where an application should access the DOM is
within directives. This is important because artifacts that access the DOM are hard to test.
If you need to access the DOM directly you should write a custom directive for this. The
{@link directive directives guide} explains how to do this.
</div>
+2 -5
View File
@@ -22,7 +22,7 @@ At a high level, directives are markers on a DOM element (such as an attribute,
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
attach a specified behavior to that DOM element or even transform the DOM element and its children.
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngView`.
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngClass`.
Much like you create controllers and services, you can create your own directives for Angular to use.
When Angular {@link guide/bootstrap bootstraps} your application, the
{@link guide/compiler HTML compiler} traverses the DOM matching directives against the DOM elements.
@@ -160,7 +160,7 @@ restrictions, you cannot simply write `cx="{{cx}}"`.
With `ng-attr-cx` you can work around this problem.
If an attribute with a binding is prefixed with the `ngAttr` prefix (denormalized as `ng-attr-`)
then during the binding will be applied to the corresponding unprefixed attribute. This allows
then during the binding it will be applied to the corresponding unprefixed attribute. This allows
you to bind to attributes that would otherwise be eagerly processed by browsers
(e.g. an SVG element's `circle[cx]` attributes). When using `ngAttr`, the `allOrNothing` flag of
{@link ng.$interpolate $interpolate} is used, so if any expression in the interpolated string
@@ -282,9 +282,6 @@ using `templateUrl` instead:
</file>
</example>
Great! But what if we wanted to have our directive match the tag name `<my-customer>` instead?
If we simply put a `<my-customer>` element into the HTML, it doesn't work.
<div class="alert alert-warning">
**Note:** When you create a directive, it is restricted to attribute and elements only by default. In order to
create directives that are triggered by class name, you need to use the `restrict` option.
+1 -1
View File
@@ -143,7 +143,7 @@ means that it will be executed one or more times during the each `$digest` cycle
Input: <input ng-model="greeting" type="text"><br>
Decoration: <input ng-model="decoration.symbol" type="text"><br>
No filter: {{greeting}}<br>
Reverse: {{greeting | decorate}}<br>
Decorated: {{greeting | decorate}}<br>
</div>
</file>
+1
View File
@@ -213,6 +213,7 @@ only when the control loses focus (blur event).
<input type="text" ng-model="user.data" /><br />
</form>
<pre>username = "{{user.name}}"</pre>
<pre>userdata = "{{user.data}}"</pre>
</div>
</file>
<file name="script.js">
+3 -2
View File
@@ -70,7 +70,7 @@ In Angular applications, you move the job of filling page templates with data fr
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/).
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/)
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/), [angular-localization](http://doshprompt.github.io/angular-localization/)
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
* **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/)
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/), [ngTagsInput](https://github.com/mbenford/ngTagsInput)
@@ -90,7 +90,7 @@ This is a short list of libraries with specific support and documentation for wo
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html)
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
@@ -105,6 +105,7 @@ This is a short list of libraries with specific support and documentation for wo
* [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
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
###Videos:
* [egghead.io](http://egghead.io/)
+2 -2
View File
@@ -9,7 +9,7 @@
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template
language and lets you extend HTML's syntax to express your application's components clearly and
succinctly. Angular's data binding and dependency injection eliminate much of the code you
currently have to write. And it all happens within the browser, making it
would otherwise have to write. And it all happens within the browser, making it
an ideal partner with any server technology.
Angular is what HTML would have been had it been designed for applications. HTML is a great
@@ -103,7 +103,7 @@ Angular frees you from the following pains:
* **Writing tons of initialization code just to get started:** Typically you need to write a lot
of plumbing just to get a basic "Hello World" AJAX app working. With Angular you can bootstrap
your app easily using services, which are auto-injected into your application in a
[Guice](http://code.google.com/p/google-guice/)-like dependency-injection style. This allows you
[Guice](https://github.com/google/guice)-like dependency-injection style. This allows you
to get started developing features quickly. As a bonus, you get full control over the
initialization process in automated tests.
+1 -1
View File
@@ -96,7 +96,7 @@ this limitation, use a regular expression object as the value for the expression
//after
$scope.exp = /abc/i;
- **Scope:** due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
Scope#$id is now of time number rather than string. Since the
Scope#$id is now of type number rather than string. Since the
id is primarily being used for debugging purposes this change should not affect
anyone.
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
+10
View File
@@ -338,6 +338,16 @@ We inject the $compile service and $rootScope before each jasmine test. The $com
to render the aGreatEye directive. After rendering the directive we ensure that the directive has
replaced the content and "lidless, wreathed in flame, 2 times" is present.
<div class="alert alert-info">
**Underscore notation**:
The use of the underscore notation (e.g.: `_$rootScope_`) is a convention wide spread in AngularJS
community to keep the variable names clean in your tests. That's why the
{@link $injector} strips out the leading and the trailing underscores when
matching the parameters. The underscore rule applies ***only*** if the name starts **and** ends with
exactly one underscore, otherwise no replacing happens.
</div>
### Testing Transclusion Directives
Directives that use transclusion are treated specially by the compiler. Before their compile
+3 -3
View File
@@ -22,7 +22,7 @@ So it's definitely not a plugin or some other native browser extension.
### Is AngularJS a templating system?
At the highest level, Angular does look like a just another templating system. But there is one
At the highest level, Angular does look like just another templating system. But there is one
important reason why the Angular templating system is different, that makes it very good fit for
application development: bidirectional data binding. The template is compiled in the browser and
the compilation step produces a live view. This means you, the developers, don't need to write
@@ -39,7 +39,7 @@ for server-side communication.
AngularJS was designed to be compatible with other security measures like Content Security Policy
(CSP), HTTPS (SSL/TLS) and server-side authentication and authorization that greatly reduce the
possible attack vectors and we highly recommended their use.
possible attack vectors and we highly recommend their use.
### Can I download the source, build, and host the AngularJS environment locally?
@@ -205,7 +205,7 @@ If you want to apply a directive to each inner piece of the repeat, put it on a
### `$rootScope` exists, but it can be used for evil
Scopes in Angular form a hierarchy, prototypically inheriting from a root scope at the top of the tree.
Scopes in Angular form a hierarchy, prototypally inheriting from a root scope at the top of the tree.
Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
Occasionally there are pieces of data that you want to make global to the whole app.
+2 -2
View File
@@ -233,7 +233,7 @@ browser is limited, which results in your karma tests running extremely slow.
Refresh your browser and verify that it says "Hello, World!".
* Update the unit test for the controller in ./test/unit/controllersSpec.js to reflect the previous change. For example by adding:
* Update the unit test for the controller in `./test/unit/controllersSpec.js` to reflect the previous change. For example by adding:
expect(scope.name).toBe('World');
@@ -251,7 +251,7 @@ browser is limited, which results in your karma tests running extremely slow.
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
</table>
Extra points: try and make an 8x8 table using an additional ng-repeat.
Extra points: try and make an 8x8 table using an additional `ng-repeat`.
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
+2 -2
View File
@@ -95,7 +95,7 @@ describe('PhoneCat App', function() {
});
it('should filter the phone list as user types into the search box', function() {
it('should filter the phone list as a user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
@@ -159,7 +159,7 @@ Let's see how we can get the current value of the `query` model to appear in the
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
it('should filter the phone list as user types into the search box', function() {
it('should filter the phone list as a user types into the search box', function() {
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
+1 -1
View File
@@ -11,7 +11,7 @@ from our server using one of Angular's built-in {@link guide/services services}
ng.$http $http}. We will use Angular's {@link guide/di dependency
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
* There are now a list of 20 phones, loaded from the server.
* There is now a list of 20 phones, loaded from the server.
<div doc-tutorial-reset="5"></div>
+6 -6
View File
@@ -33,17 +33,17 @@ We are using [Bower][bower] to install client side dependencies. This step upda
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.x",
"jquery": "1.10.2",
"angular": "~1.3.0",
"angular-mocks": "~1.3.0",
"jquery": "2.1.1",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.x"
"angular-route": "~1.3.0"
}
}
```
The new dependency `"angular-route": "~1.2.x"` tells bower to install a version of the
angular-route component that is compatible with version 1.2.x. We must tell bower to download
The new dependency `"angular-route": "~1.3.0"` tells bower to install a version of the
angular-route component that is compatible with version 1.3.x. We must tell bower to download
and install this dependency.
If you have bower installed globally then you can run `bower install` but for this project we have
+6 -6
View File
@@ -32,17 +32,17 @@ We are using [Bower][bower] to install client side dependencies. This step upda
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.x",
"angular": "~1.3.0",
"angular-mocks": "~1.3.0",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.x",
"angular-resource": "~1.2.x"
"angular-route": "~1.3.0",
"angular-resource": "~1.3.0"
}
}
```
The new dependency `"angular-resource": "~1.2.x"` tells bower to install a version of the
angular-resource component that is compatible with version 1.2.x. We must ask bower to download
The new dependency `"angular-resource": "~1.3.0"` tells bower to install a version of the
angular-resource component that is compatible with version 1.3.x. We must ask bower to download
and install this dependency. We can do this by running:
```
+15 -15
View File
@@ -21,7 +21,7 @@ animations on top of the template code we created before.
## Dependencies
The animation functionality is provided by Angular in the `ngAnimate` module, which is distributed
separately from the core Angular framework. In addition we will use `JQuery` in this project to do
separately from the core Angular framework. In addition we will use `jQuery` in this project to do
extra JavaScript animations.
We are using [Bower][bower] to install client side dependencies. This step updates the
@@ -36,21 +36,21 @@ We are using [Bower][bower] to install client side dependencies. This step upda
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.x",
"angular": "~1.3.0",
"angular-mocks": "~1.3.0",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.x",
"angular-resource": "~1.2.x",
"jquery": "1.10.2",
"angular-animate": "~1.2.x"
"angular-route": "~1.3.0",
"angular-resource": "~1.3.0",
"jquery": "~2.1.1",
"angular-animate": "~1.3.0"
}
}
```
* `"angular-animate": "~1.2.x"` tells bower to install a version of the
angular-animate component that is compatible with version 1.2.x.
* `"jquery": "1.10.2"` tells bower to install the 1.10.2 version of JQuery. Note that this is not an
Angular library, it is the standard JQuery library. We can use bower to install a wide range of 3rd
* `"angular-animate": "~1.3.0"` tells bower to install a version of the
angular-animate component that is compatible with version 1.3.x.
* `"jquery": "2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an
Angular library, it is the standard jQuery library. We can use bower to install a wide range of 3rd
party libraries.
We must ask bower to download and install this dependency. We can do this by running:
@@ -255,7 +255,7 @@ which are described in detail below.
Next let's add an animation for transitions between route changes in {@link api/ngRoute.directive:ngView `ngView`}.
To start, let's add a new CSS class to our HTML like we did in the example above.
This time, instead of the `ng-repeat` element, let's add it to the element containing the ng-view directive.
This time, instead of the `ng-repeat` element, let's add it to the element containing the `ng-view` directive.
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.
@@ -340,13 +340,13 @@ a cross fade animation in between. So as the previous page is just about to be r
while the new page fades in right on top of it.
Once the leave animation is over then element is removed and once the enter animation is complete
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to
be position itself with its default CSS code (so no more absolute positioning once the animation is
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element, causing it to rerender and
reposition itself with its default CSS code (so no more absolute positioning once the animation is
over). This works fluidly so that pages flow naturally between route changes without anything
jumping around.
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time
a new page is loaded the ng-view directive will create a copy of itself, download the template and
a new page is loaded the `ng-view` directive will create a copy of itself, download the template and
append the contents. This ensures that all views are contained within a single HTML element which
allows for easy animation control.
+37 -5
View File
@@ -8,7 +8,10 @@ var bower = require('bower');
var Dgeni = require('dgeni');
var merge = require('event-stream').merge;
var path = require('canonical-path');
var foreach = require('gulp-foreach');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');
// We indicate to gulp that tasks are async by returning the stream.
// Gulp can then wait for the stream to close before starting dependent tasks.
@@ -17,6 +20,9 @@ var path = require('canonical-path');
var outputFolder = '../build/docs';
var bowerFolder = 'bower_components';
var src = 'app/src/**/*.js';
var assets = 'app/assets/**/*';
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
pattern = pattern || '/**/*';
@@ -40,14 +46,37 @@ gulp.task('bower', function() {
});
gulp.task('build-app', function() {
gulp.src('app/src/**/*.js')
.pipe(concat('docs.js'))
.pipe(gulp.dest(outputFolder + '/js/'));
var file = 'docs.js';
var minFile = 'docs.min.js';
var folder = outputFolder + '/js/';
return gulp.src(src)
.pipe(sourcemaps.init())
.pipe(concat(file))
.pipe(gulp.dest(folder))
.pipe(rename(minFile))
.pipe(uglify())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(folder));
});
gulp.task('assets', ['bower'], function() {
var JS_EXT = /\.js$/;
return merge(
gulp.src(['app/assets/**/*']).pipe(gulp.dest(outputFolder)),
gulp.src([assets])
.pipe(gulp.dest(outputFolder)),
gulp.src([assets])
.pipe(foreach(function(stream, file) {
if (JS_EXT.test(file.relative)) {
var minFile = file.relative.replace(JS_EXT, '.min.js');
return stream
.pipe(sourcemaps.init())
.pipe(concat(minFile))
.pipe(uglify())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(outputFolder));
}
})),
copyComponent('bootstrap', '/dist/**/*'),
copyComponent('open-sans-fontface'),
copyComponent('lunr.js','/*.js'),
@@ -77,3 +106,6 @@ gulp.task('jshint', ['doc-gen'], function() {
// The default task that will be run if no task is supplied
gulp.task('default', ['assets', 'doc-gen', 'build-app', 'jshint']);
gulp.task('watch', function() {
gulp.watch([src, assets], ['assets', 'build-app']);
});
+8 -4
View File
@@ -285,11 +285,15 @@ module.exports = {
//csp connect middleware
csp: function(){
conditionalCsp: function(){
return function(req, res, next){
res.setHeader("X-WebKit-CSP", "default-src 'self';");
res.setHeader("X-Content-Security-Policy", "default-src 'self'");
res.setHeader("Content-Security-Policy", "default-src 'self'");
var CSP = /\.csp\W/;
if (CSP.test(req.url)) {
res.setHeader("X-WebKit-CSP", "default-src 'self';");
res.setHeader("X-Content-Security-Policy", "default-src 'self'");
res.setHeader("Content-Security-Policy", "default-src 'self'");
}
next();
};
},
+2
View File
@@ -83,6 +83,7 @@ var getTaggedVersion = function() {
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
version.codeName = getCodeName(tag);
version.full = version.version;
version.branch = 'v' + currentPackage.branchVersion.replace('*', 'x');
return version;
}
}
@@ -197,6 +198,7 @@ var getSnapshotVersion = function() {
version.isSnapshot = true;
version.format();
version.full = version.version + '+' + version.build;
version.branch = 'master';
return version;
};
+829 -341
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -13,7 +13,6 @@
"canonical-path": "0.0.2",
"dgeni": "^0.4.0",
"dgeni-packages": "^0.10.0",
"es6-shim": "^0.14.0",
"event-stream": "~3.1.0",
"grunt": "~0.4.2",
"grunt-bump": "~0.0.13",
@@ -29,8 +28,12 @@
"grunt-parallel": "~0.3.1",
"grunt-shell": "~0.4.0",
"gulp": "~3.8.0",
"gulp-concat": "~2.1.7",
"gulp-concat": "^2.4.1",
"gulp-foreach": "0.0.1",
"gulp-jshint": "~1.4.2",
"gulp-rename": "^1.2.0",
"gulp-sourcemaps": "^1.2.2",
"gulp-uglify": "^1.0.1",
"gulp-util": "^3.0.1",
"jasmine-node": "~1.11.0",
"jasmine-reporters": "~0.2.1",
@@ -49,7 +52,7 @@
"marked": "~0.3.0",
"node-html-encoder": "0.0.2",
"promises-aplus-tests": "~2.0.4",
"protractor": "1.2.0",
"protractor": "1.3.1",
"q": "~1.0.0",
"q-io": "^1.10.9",
"qq": "^0.3.5",
@@ -57,9 +60,7 @@
"semver": "~2.1.0",
"shelljs": "~0.2.6",
"sorted-object": "^1.0.0",
"stringmap": "^0.2.2",
"winston": "~0.7.2",
"yaml-js": "~0.0.8"
"stringmap": "^0.2.2"
},
"licenses": [
{
+2 -2
View File
@@ -21,9 +21,9 @@ exports.config = {
// Disable animations so e2e tests run more quickly
var disableNgAnimate = function() {
angular.module('disableNgAnimate', []).run(function($animate) {
angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
$animate.enabled(false);
});
}]);
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
+2 -2
View File
@@ -12,9 +12,9 @@ exports.config = {
// Disable animations so e2e tests run more quickly
var disableNgAnimate = function() {
angular.module('disableNgAnimate', []).run(function($animate) {
angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
$animate.enabled(false);
});
}]);
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# Untags a release.
echo "###################################"
echo "## Untag angular.js for a release #"
echo "###################################"
ARG_DEFS=(
"--action=(prepare|publish)"
# the version number of the release.
# e.g. 1.2.12 or 1.2.12-rc.1
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
)
function init {
TMP_DIR=$(resolveDir ../../tmp)
TAG_NAME="v$VERSION_NUMBER"
}
function prepare() {
:
}
function publish() {
# push the tag deletion to github
tags=`git ls-remote --tags git@github.com:angular/angular.js`
if [[ $tags =~ "refs/tags/v$VERSION_NUMBER^" ]]; then
echo "-- Creating dummy git repo for angular.js with origin remote"
mkdir $TMP_DIR/empty-angular.js
cd $TMP_DIR/empty-angular.js
git init
git remote add origin git@github.com:angular/angular.js.git
git push origin ":$TAG_NAME"
else
echo "-- Tag v$VERSION_NUMBER does not exist on remote. Moving on"
fi
}
source $(dirname $0)/../utils.inc
+15
View File
@@ -17,6 +17,7 @@ function init {
REPOS=(
angular
angular-animate
angular-aria
angular-cookies
angular-i18n
angular-loader
@@ -72,6 +73,8 @@ function prepare {
cd $TMP_DIR/bower-$repo
replaceJsonProp "bower.json" "version" ".*" "$NEW_VERSION"
replaceJsonProp "bower.json" "angular.*" ".*" "$NEW_VERSION"
replaceJsonProp "package.json" "version" ".*" "$NEW_VERSION"
replaceJsonProp "package.json" "angular.*" ".*" "$NEW_VERSION"
git add -A
@@ -89,6 +92,18 @@ function publish {
cd $TMP_DIR/bower-$repo
git push origin master
git push origin v$NEW_VERSION
# don't publish every build to npm
if [ "${NEW_VERSION/+sha}" = "$NEW_VERSION" ] ; then
if [ "${NEW_VERSION/-}" = "$NEW_VERSION" ] ; then
# publish releases as "latest"
npm publish
else
# publish prerelease builds with the beta tag
npm publish --tag=beta
fi
fi
cd $SCRIPT_DIR
done
}
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Script for removing tags from the Angular bower repos
echo "#################################"
echo "#### Untag bower ################"
echo "#################################"
ARG_DEFS=(
"--action=(prepare|publish)"
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
)
function init {
TMP_DIR=$(resolveDir ../../tmp)
REPOS=(
angular
angular-animate
angular-cookies
angular-i18n
angular-loader
angular-mocks
angular-route
angular-resource
angular-sanitize
angular-scenario
angular-touch
)
}
function prepare {
:
}
function publish {
for repo in "${REPOS[@]}"
do
tags=`git ls-remote --tags git@github.com:angular/bower-$repo`
if [[ $tags =~ "refs/tags/v$VERSION_NUMBER" ]]; then
echo "-- Creating dummy git repo for bower-$repo with origin remote"
mkdir $TMP_DIR/bower-$repo
cd $TMP_DIR/bower-$repo
git init
git remote add origin git@github.com:angular/bower-$repo.git
git push origin :v$VERSION_NUMBER
echo "-- Deleting v$VERSION_NUMBER tag from bower-$repo"
cd $SCRIPT_DIR
else
echo "-- No remote tag matching v$VERSION_NUMBER exists on bower-$repo"
fi
done
}
source $(dirname $0)/../utils.inc
+1 -1
View File
@@ -3,7 +3,7 @@
# Script for updating code.angularjs.org repo from current local build.
echo "#################################"
echo "## Update code.angular.js.org ###"
echo "## Update code.angularjs.org ###"
echo "#################################"
ARG_DEFS=(
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
# Script for removing specified release dir from code.angularjs.org.
echo "################################################"
echo "## Remove a version from code.angular.js.org ###"
echo "################################################"
ARG_DEFS=(
"--action=(prepare|publish)"
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
)
function init {
TMP_DIR=$(resolveDir ../../tmp)
REPO_DIR=$TMP_DIR/code.angularjs.org
echo "code tmp $TMP_DIR"
}
function prepare {
echo "-- Cloning code.angularjs.org"
git clone git@github.com:angular/code.angularjs.org.git $REPO_DIR
#
# Remove the files from the repo
#
echo "-- Removing $VERSION_NUMBER from code.angularjs.org"
cd $REPO_DIR
if [ -d "$VERSION_NUMBER" ]; then
git rm -r $VERSION_NUMBER
echo "-- Committing removal to code.angularjs.org"
git commit -m "removing v$VERSION_NUMBER"
else
echo "-- Version: $VERSION_NUMBER does not exist in code.angularjs.org!"
fi
}
function publish {
cd $REPO_DIR
echo "-- Pushing code.angularjs.org to github"
git push origin master
}
source $(dirname $0)/../utils.inc
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
echo "#################################"
echo "#### undo a release ############"
echo "#################################"
ARG_DEFS=(
# require the git dryrun flag so the script can't be run without
# thinking about this!
"--git-push-dryrun=(true|false)"
# the version number of the release.
# e.g. 1.2.12 or 1.2.12-rc.1
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
)
function init {
if [[ ! $VERBOSE ]]; then
VERBOSE=false
fi
VERBOSE_ARG="--verbose=$VERBOSE"
}
function phase {
ACTION_ARG="--action=$1"
VERSION_NUMBER_ARG="--version-number=$VERSION_NUMBER"
../angular.js/untag-release.sh $ACTION_ARG $VERBOSE_ARG\
--version-number=$VERSION_NUMBER
../code.angularjs.org/unpublish.sh $ACTION_ARG $VERSION_NUMBER_ARG $VERBOSE_ARG
../bower/unpublish.sh $ACTION_ARG $VERSION_NUMBER_ARG $VERBOSE_ARG
}
function run {
# First prepare all scripts (build, commit, tag, ...),
# so we are sure everything is all right
phase prepare
# only then publish to github
phase publish
}
source $(dirname $0)/../utils.inc
+8 -1
View File
@@ -11,6 +11,7 @@
"jqLite": false,
"jQuery": false,
"slice": false,
"splice": false,
"push": false,
"toString": false,
"ngMinErr": false,
@@ -55,7 +56,6 @@
"trim": false,
"isElement": false,
"makeMap": false,
"map": false,
"size": false,
"includes": false,
"arrayRemove": false,
@@ -92,6 +92,13 @@
"skipDestroyOnNextJQueryCleanData": true,
"NODE_TYPE_ELEMENT": false,
"NODE_TYPE_TEXT": false,
"NODE_TYPE_COMMENT": false,
"NODE_TYPE_COMMENT": false,
"NODE_TYPE_DOCUMENT": false,
"NODE_TYPE_DOCUMENT_FRAGMENT": false,
/* filters.js */
"getFirstThursdayOfYear": false,
+20 -21
View File
@@ -6,6 +6,7 @@
jqLite: true,
jQuery: true,
slice: true,
splice: true,
push: true,
toString: true,
ngMinErr: true,
@@ -50,7 +51,6 @@
trim: true,
isElement: true,
makeMap: true,
map: true,
size: true,
includes: true,
arrayRemove: true,
@@ -83,6 +83,12 @@
getBlockNodes: true,
hasOwnProperty: true,
createMap: true,
NODE_TYPE_ELEMENT: true,
NODE_TYPE_TEXT: true,
NODE_TYPE_COMMENT: true,
NODE_TYPE_DOCUMENT: true,
NODE_TYPE_DOCUMENT_FRAGMENT: true,
*/
////////////////////////////////////
@@ -162,6 +168,7 @@ var /** holds major version number for IE or NaN for real browsers */
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
splice = [].splice,
push = [].push,
toString = Object.prototype.toString,
ngMinErr = minErr('ng'),
@@ -172,13 +179,10 @@ var /** holds major version number for IE or NaN for real browsers */
uid = 0;
/**
* IE 11 changed the format of the UserAgent string.
* See http://msdn.microsoft.com/en-us/library/ms537503.aspx
* documentMode is an IE-only property
* http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx
*/
msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
if (isNaN(msie)) {
msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
}
msie = document.documentMode;
/**
@@ -194,7 +198,7 @@ function isArrayLike(obj) {
var length = obj.length;
if (obj.nodeType === 1 && length) {
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
}
@@ -327,7 +331,7 @@ function setHashKey(obj, h) {
* @kind function
*
* @description
* Extends the destination object `dst` by copying all of the properties from the `src` object(s)
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects.
*
* @param {Object} dst Destination object.
@@ -617,15 +621,6 @@ function nodeName_(element) {
}
function map(obj, iterator, context) {
var results = [];
forEach(obj, function(value, index, list) {
results.push(iterator.call(context, value, index, list));
});
return results;
}
/**
* @description
* Determines the number of elements in an array, the number of properties an object has, or
@@ -1039,11 +1034,9 @@ function startingTag(element) {
// are not allowed to have children. So we just ignore it.
element.empty();
} catch(e) {}
// As Per DOM Standards
var TEXT_NODE = 3;
var elemHtml = jqLite('<div>').append(element).html();
try {
return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
elemHtml.
match(/^(<[^>]+>)/)[1].
replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });
@@ -1621,3 +1614,9 @@ function getBlockNodes(nodes) {
function createMap() {
return Object.create(null);
}
var NODE_TYPE_ELEMENT = 1;
var NODE_TYPE_TEXT = 3;
var NODE_TYPE_COMMENT = 8;
var NODE_TYPE_DOCUMENT = 9;
var NODE_TYPE_DOCUMENT_FRAGMENT = 11;
+1 -2
View File
@@ -140,8 +140,7 @@ function publishExternalAPI(angular){
'getTestability': getTestability,
'$$minErr': minErr,
'$$csp': csp,
'reloadWithDebugInfo': reloadWithDebugInfo,
'$$hasClass': jqLiteHasClass
'reloadWithDebugInfo': reloadWithDebugInfo
});
angularModule = setupModuleLoader(window);
+21 -8
View File
@@ -7,13 +7,13 @@
* @kind function
*
* @description
* Creates an injector function that can be used for retrieving services as well as for
* Creates an injector object that can be used for retrieving services as well as for
* dependency injection (see {@link guide/di dependency injection}).
*
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See
* {@link angular.module}. The `ng` module must be explicitly added.
* @returns {function()} Injector function. See {@link auto.$injector $injector}.
* @returns {function()} Injector object. See {@link auto.$injector $injector}.
*
* @example
* Typical usage
@@ -120,7 +120,6 @@ function annotate(fn, strictDi, name) {
/**
* @ngdoc service
* @name $injector
* @kind function
*
* @description
*
@@ -135,7 +134,7 @@ function annotate(fn, strictDi, name) {
* expect($injector.get('$injector')).toBe($injector);
* expect($injector.invoke(function($injector) {
* return $injector;
* }).toBe($injector);
* })).toBe($injector);
* ```
*
* # Injection Function Annotation
@@ -202,8 +201,8 @@ function annotate(fn, strictDi, name) {
* @description
* Allows the user to query if the particular service exists.
*
* @param {string} Name of the service to query.
* @returns {boolean} returns true if injector has given service.
* @param {string} name Name of the service to query.
* @returns {boolean} `true` if injector has given service.
*/
/**
@@ -663,7 +662,21 @@ function createInjector(modulesToLoad, strictDi) {
return providerCache[name + providerSuffix] = provider_;
}
function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
function enforceReturnValue(name, factory) {
return function enforcedReturnValue() {
var result = instanceInjector.invoke(factory);
if (isUndefined(result)) {
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
}
return result;
};
}
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
@@ -671,7 +684,7 @@ function createInjector(modulesToLoad, strictDi) {
}]);
}
function value(name, val) { return factory(name, valueFn(val)); }
function value(name, val) { return factory(name, valueFn(val), false); }
function constant(name, value) {
assertNotHasOwnProperty(name, 'constant');
+9 -9
View File
@@ -44,7 +44,7 @@
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
* - [`css()`](http://api.jquery.com/css/)
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
* - [`data()`](http://api.jquery.com/data/)
* - [`detach()`](http://api.jquery.com/detach/)
* - [`empty()`](http://api.jquery.com/empty/)
@@ -166,7 +166,7 @@ function jqLiteAcceptsData(node) {
// The window object can accept data but has no nodeType
// Otherwise we are only interested in elements (1) and documents (9)
var nodeType = node.nodeType;
return nodeType === 1 || !nodeType || nodeType === 9;
return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
}
function jqLiteBuildFragment(html, context) {
@@ -419,7 +419,7 @@ function jqLiteController(element, name) {
function jqLiteInheritedData(element, name, value) {
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
if(element.nodeType == 9) {
if(element.nodeType == NODE_TYPE_DOCUMENT) {
element = element.documentElement;
}
var names = isArray(name) ? name : [name];
@@ -432,7 +432,7 @@ function jqLiteInheritedData(element, name, value) {
// If dealing with a document fragment node with a host element, and no parent, use the host
// element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
// to lookup parent controllers.
element = element.parentNode || (element.nodeType === 11 && element.host);
element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
}
}
@@ -610,7 +610,7 @@ forEach({
function getText(element, value) {
if (isUndefined(value)) {
var nodeType = element.nodeType;
return (nodeType === 1 || nodeType === 3) ? element.textContent : '';
return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : '';
}
element.textContent = value;
}
@@ -832,7 +832,7 @@ forEach({
children: function(element) {
var children = [];
forEach(element.childNodes, function(element){
if (element.nodeType === 1)
if (element.nodeType === NODE_TYPE_ELEMENT)
children.push(element);
});
return children;
@@ -844,7 +844,7 @@ forEach({
append: function(element, node) {
var nodeType = element.nodeType;
if (nodeType !== 1 && nodeType !== 11) return;
if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return;
node = new JQLite(node);
@@ -855,7 +855,7 @@ forEach({
},
prepend: function(element, node) {
if (element.nodeType === 1) {
if (element.nodeType === NODE_TYPE_ELEMENT) {
var index = element.firstChild;
forEach(new JQLite(node), function(child){
element.insertBefore(child, index);
@@ -906,7 +906,7 @@ forEach({
parent: function(element) {
var parent = element.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null;
},
next: function(element) {
+4 -1
View File
@@ -94,7 +94,10 @@ function $AnchorScrollProvider() {
// (no url change, no $location.hash() change), browser native does scroll
if (autoScrollingEnabled) {
$rootScope.$watch(function autoScrollWatch() {return $location.hash();},
function autoScrollWatchAction() {
function autoScrollWatchAction(newVal, oldVal) {
// skip the initial scroll if $location.hash is empty
if (newVal === oldVal && newVal === '') return;
$rootScope.$evalAsync(scroll);
});
}
+106 -6
View File
@@ -81,9 +81,57 @@ var $AnimateProvider = ['$provide', function($provide) {
return this.$$classNameFilter;
};
this.$get = ['$$q', '$$asyncCallback', function($$q, $$asyncCallback) {
this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) {
var currentDefer;
function runAnimationPostDigest(fn) {
var cancelFn, defer = $$q.defer();
defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
cancelFn && cancelFn();
};
$rootScope.$$postDigest(function ngAnimatePostDigest() {
cancelFn = fn(function ngAnimateNotifyComplete() {
defer.resolve();
});
});
return defer.promise;
}
function resolveElementClasses(element, cache) {
var toAdd = [], toRemove = [];
var hasClasses = createMap();
forEach((element.attr('class') || '').split(/\s+/), function(className) {
hasClasses[className] = true;
});
forEach(cache.classes, function(status, className) {
var hasClass = hasClasses[className];
// If the most recent class manipulation (via $animate) was to remove the class, and the
// element currently has the class, the class is scheduled for removal. Otherwise, if
// the most recent class manipulation (via $animate) was to add the class, and the
// element does not currently have the class, the class is scheduled to be added.
if (status === false && hasClass) {
toRemove.push(className);
} else if (status === true && !hasClass) {
toAdd.push(className);
}
});
return (toAdd.length + toRemove.length) > 0 && [toAdd.length && toAdd, toRemove.length && toRemove];
}
function cachedClassManipulation(cache, classes, op) {
for (var i=0, ii = classes.length; i < ii; ++i) {
var className = classes[i];
cache[className] = op;
}
}
function asyncPromise() {
// only serve one instance of a promise in order to save CPU cycles
if (!currentDefer) {
@@ -187,13 +235,17 @@ var $AnimateProvider = ['$provide', function($provide) {
* @return {Promise} the animation callback promise
*/
addClass : function(element, className) {
return this.setClass(element, className, []);
},
$$addClassImmediately : function addClassImmediately(element, className) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function (element) {
jqLiteAddClass(element, className);
});
return asyncPromise();
},
/**
@@ -209,6 +261,11 @@ var $AnimateProvider = ['$provide', function($provide) {
* @return {Promise} the animation callback promise
*/
removeClass : function(element, className) {
return this.setClass(element, [], className);
},
$$removeClassImmediately : function removeClassImmediately(element, className) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
@@ -231,10 +288,53 @@ var $AnimateProvider = ['$provide', function($provide) {
* @param {string} remove the CSS class which will be removed from the element
* @return {Promise} the animation callback promise
*/
setClass : function(element, add, remove) {
this.addClass(element, add);
this.removeClass(element, remove);
return asyncPromise();
setClass : function(element, add, remove, runSynchronously) {
var self = this;
var STORAGE_KEY = '$$animateClasses';
var createdCache = false;
element = jqLite(element);
if (runSynchronously) {
// TODO(@caitp/@matsko): Remove undocumented `runSynchronously` parameter, and always
// perform DOM manipulation asynchronously or in postDigest.
self.$$addClassImmediately(element, add);
self.$$removeClassImmediately(element, remove);
return asyncPromise();
}
var cache = element.data(STORAGE_KEY);
if (!cache) {
cache = {
classes: {}
};
createdCache = true;
}
var classes = cache.classes;
add = isArray(add) ? add : add.split(' ');
remove = isArray(remove) ? remove : remove.split(' ');
cachedClassManipulation(classes, add, true);
cachedClassManipulation(classes, remove, false);
if (createdCache) {
cache.promise = runAnimationPostDigest(function(done) {
var cache = element.data(STORAGE_KEY);
element.removeData(STORAGE_KEY);
var classes = cache && resolveElementClasses(element, cache);
if (classes) {
if (classes[0]) self.$$addClassImmediately(element, classes[0]);
if (classes[1]) self.$$removeClassImmediately(element, classes[1]);
}
done();
});
element.data(STORAGE_KEY, cache);
}
return cache.promise;
},
enabled : noop,
+52 -21
View File
@@ -1,4 +1,5 @@
'use strict';
/* global stripHash: true */
/**
* ! This is a private undocumented service !
@@ -123,8 +124,9 @@ function Browser(window, document, $log, $sniffer) {
//////////////////////////////////////////////////////////////
var lastBrowserUrl = location.href,
lastHistoryState = history.state,
baseElement = document.find('base'),
newLocation = null;
reloadLocation = null;
/**
* @name $browser#url
@@ -143,26 +145,42 @@ function Browser(window, document, $log, $sniffer) {
* {@link ng.$location $location service} to change url.
*
* @param {string} url New url (when used as setter)
* @param {boolean=} replace Should new url replace current history record ?
* @param {boolean=} replace Should new url replace current history record?
* @param {object=} state object to use with pushState/replaceState
*/
self.url = function(url, replace) {
self.url = function(url, replace, state) {
// In modern browsers `history.state` is `null` by default; treating it separately
// from `undefined` would cause `$browser.url('/foo')` to change `history.state`
// to undefined via `pushState`. Instead, let's change `undefined` to `null` here.
if (isUndefined(state)) {
state = null;
}
// Android Browser BFCache causes location, history reference to become stale.
if (location !== window.location) location = window.location;
if (history !== window.history) history = window.history;
// setter
if (url) {
if (lastBrowserUrl == url) return;
// Don't change anything if previous and current URLs and states match. This also prevents
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
// See https://github.com/angular/angular.js/commit/ffb2701
if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) {
return;
}
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
lastBrowserUrl = url;
if ($sniffer.history) {
if (replace) history.replaceState(null, '', url);
else {
history.pushState(null, '', url);
// Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462
baseElement.attr('href', baseElement.attr('href'));
}
// Don't use history API if only the hash changed
// due to a bug in IE10/IE11 which leads
// to not firing a `hashchange` nor `popstate` event
// in some cases (see #9143).
if ($sniffer.history && (!sameBase || history.state !== state)) {
history[replace ? 'replaceState' : 'pushState'](state, '', url);
lastHistoryState = history.state;
} else {
newLocation = url;
if (!sameBase) {
reloadLocation = url;
}
if (replace) {
location.replace(url);
} else {
@@ -172,23 +190,38 @@ function Browser(window, document, $log, $sniffer) {
return self;
// getter
} else {
// - newLocation is a workaround for an IE7-9 issue with location.replace and location.href
// methods not updating location.href synchronously.
// - reloadLocation is needed as browsers don't allow to read out
// the new location.href if a reload happened.
// - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172
return newLocation || location.href.replace(/%27/g,"'");
return reloadLocation || location.href.replace(/%27/g,"'");
}
};
/**
* @name $browser#state
*
* @description
* This method is a getter.
*
* Return history.state or null if history.state is undefined.
*
* @returns {object} state
*/
self.state = function() {
return isUndefined(history.state) ? null : history.state;
};
var urlChangeListeners = [],
urlChangeInit = false;
function fireUrlChange() {
newLocation = null;
if (lastBrowserUrl == self.url()) return;
if (lastBrowserUrl === self.url() && lastHistoryState === history.state) {
return;
}
lastBrowserUrl = self.url();
forEach(urlChangeListeners, function(listener) {
listener(self.url());
listener(self.url(), history.state);
});
}
@@ -223,9 +256,7 @@ function Browser(window, document, $log, $sniffer) {
// html5 history api - popstate event
if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange);
// hashchange event
if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange);
// polling
else self.addPollFn(fireUrlChange);
jqLite(window).on('hashchange', fireUrlChange);
urlChangeInit = true;
}
+266 -88
View File
@@ -212,8 +212,11 @@
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
* * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found.
* * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found.
* * `?^` - Attempt to locate the required controller by searching the element and its parents or pass
* `null` to the `link` fn if not found.
* * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass
* `null` to the `link` fn if not found.
*
*
* #### `controllerAs`
@@ -291,22 +294,18 @@
* (because SVG doesn't work with custom elements in the DOM tree).
*
* #### `transclude`
* compile the content of the element and make it available to the directive.
* Typically used with {@link ng.directive:ngTransclude
* ngTransclude}. The advantage of transclusion is that the linking function receives a
* transclusion function which is pre-bound to the correct scope. In a typical setup the widget
* creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate`
* scope. This makes it possible for the widget to have private state, and the transclusion to
* be bound to the parent (pre-`isolate`) scope.
* Extract the contents of the element where the directive appears and make it available to the directive.
* The contents are compiled and provided to the directive as a **transclusion function**. See the
* {@link $compile#transclusion Transclusion} section below.
*
* * `true` - transclude the content of the directive.
* * `'element'` - transclude the whole element including any directives defined at lower priority.
* There are two kinds of transclusion depending upon whether you want to transclude just the contents of the
* directive's element or the entire element:
*
* * `true` - transclude the content (i.e. the child nodes) of the directive's element.
* * `'element'` - transclude the whole of the directive's element including any directives on this
* element that defined at a lower priority than this directive. When used, the `template`
* property is ignored.
*
* <div class="alert alert-warning">
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
* DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
* Testing Transclusion Directives}.
* </div>
*
* #### `compile`
*
@@ -404,7 +403,121 @@
* It is safe to do DOM transformation in the post-linking function on elements that are not waiting
* for their async templates to be resolved.
*
* <a name="Attributes"></a>
*
* ### Transclusion
*
* Transclusion is the process of extracting a collection of DOM element from one part of the DOM and
* copying them to another part of the DOM, while maintaining their connection to the original AngularJS
* scope from where they were taken.
*
* Transclusion is used (often with {@link ngTransclude}) to insert the
* original contents of a directive's element into a specified place in the template of the directive.
* The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded
* content has access to the properties on the scope from which it was taken, even if the directive
* has isolated scope.
* See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}.
*
* This makes it possible for the widget to have private state for its template, while the transcluded
* content has access to its originating scope.
*
* <div class="alert alert-warning">
* **Note:** When testing an element transclude directive you must not place the directive at the root of the
* DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives
* Testing Transclusion Directives}.
* </div>
*
* #### Transclusion Functions
*
* When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion
* function** to the directive's `link` function and `controller`. This transclusion function is a special
* **linking function** that will return the compiled contents linked to a new transclusion scope.
*
* <div class="alert alert-info">
* If you are just using {@link ngTransclude} then you don't need to worry about this function, since
* ngTransclude will deal with it for us.
* </div>
*
* If you want to manually control the insertion and removal of the transcluded content in your directive
* then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery
* object that contains the compiled DOM, which is linked to the correct transclusion scope.
*
* When you call a transclusion function you can pass in a **clone attach function**. This function is accepts
* two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded
* content and the `scope` is the newly created transclusion scope, to which the clone is bound.
*
* <div class="alert alert-info">
* **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function
* since you then get a fresh clone of the original DOM and also have access to the new transclusion scope.
* </div>
*
* It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone
* attach function**:
*
* ```js
* var transcludedContent, transclusionScope;
*
* $transclude(function(clone, scope) {
* element.append(clone);
* transcludedContent = clone;
* transclusionScope = scope;
* });
* ```
*
* Later, if you want to remove the transcluded content from your DOM then you should also destroy the
* associated transclusion scope:
*
* ```js
* transcludedContent.remove();
* transclusionScope.$destroy();
* ```
*
* <div class="alert alert-info">
* **Best Practice**: if you intend to add and remove transcluded content manually in your directive
* (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it),
* then you are also responsible for calling `$destroy` on the transclusion scope.
* </div>
*
* The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat}
* automatically destroy their transluded clones as necessary so you do not need to worry about this if
* you are simply using {@link ngTransclude} to inject the transclusion into your directive.
*
*
* #### Transclusion Scopes
*
* When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion
* scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed
* when the directive's scope gets destroyed) but it inherits the properties of the scope from which it
* was taken.
*
* For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look
* like this:
*
* ```html
* <div ng-app>
* <div isolate>
* <div transclusion>
* </div>
* </div>
* </div>
* ```
*
* The `$parent` scope hierarchy will look like this:
*
* ```
* - $rootScope
* - isolate
* - transclusion
* ```
*
* but the scopes will inherit prototypically from different scopes to their `$parent`.
*
* ```
* - $rootScope
* - transclusion
* - isolate
* ```
*
*
* ### Attributes
*
* The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the
@@ -442,7 +555,7 @@
* }
* ```
*
* Below is an example using `$compileProvider`.
* ## Example
*
* <div class="alert alert-warning">
* **Note**: Typically directives are registered with `module.directive`. The example below is
@@ -567,7 +680,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/,
ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset');
ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'),
REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -872,10 +986,44 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeName = nodeName_(this.$$element);
// sanitize a[href] and img[src] values
if ((nodeName === 'a' && key === 'href') ||
(nodeName === 'img' && key === 'src')) {
// sanitize a[href] and img[src] values
this[key] = value = $$sanitizeUri(value, key === 'src');
} else if (nodeName === 'img' && key === 'srcset') {
// sanitize img[srcset] values
var result = "";
// first check if there are spaces because it's not the same pattern
var trimmedSrcset = trim(value);
// ( 999x ,| 999w ,| ,|, )
var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/;
var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/;
// split srcset into tuple of uri and descriptor except for the last item
var rawUris = trimmedSrcset.split(pattern);
// for each tuples
var nbrUrisWith2parts = Math.floor(rawUris.length / 2);
for (var i=0; i<nbrUrisWith2parts; i++) {
var innerIdx = i*2;
// sanitize the uri
result += $$sanitizeUri(trim( rawUris[innerIdx]), true);
// add the descriptor
result += ( " " + trim(rawUris[innerIdx+1]));
}
// split the last item into uri and descriptor
var lastTuple = trim(rawUris[i*2]).split(/\s/);
// sanitize the last uri
result += $$sanitizeUri(trim(lastTuple[0]), true);
// and add the last descriptor if any
if( lastTuple.length === 2) {
result += (" " + trim(lastTuple[1]));
}
this[key] = value = result;
}
if (writeAttr !== false) {
@@ -913,12 +1061,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* @param {string} key Normalized key. (ie ngAttribute) .
* @param {function(interpolatedValue)} fn Function that will be called whenever
the interpolated value of the attribute changes.
* See the {@link guide/directive#Attributes Directives} guide for more info.
* See {@link ng.$compile#attributes $compile} for more info.
* @returns {function()} Returns a deregistration function for this observer.
*/
$observe: function(key, fn) {
var attrs = this,
$$observers = (attrs.$$observers || (attrs.$$observers = {})),
$$observers = (attrs.$$observers || (attrs.$$observers = Object.create(null))),
listeners = ($$observers[key] || ($$observers[key] = []));
listeners.push(fn);
@@ -994,7 +1142,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// We can not compile top level text elements since text nodes can be merged and we will
// not be able to attach scope data to them, so we will wrap them in <span>
forEach($compileNodes, function(node, index){
if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) {
$compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0];
}
});
@@ -1003,27 +1151,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
maxPriority, ignoreDirective, previousCompileContext);
compile.$$addScopeClass($compileNodes);
var namespace = null;
var namespaceAdaptedCompileNodes = $compileNodes;
var lastCompileNode;
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
assertArg(scope, 'scope');
if (!namespace) {
namespace = detectNamespaceForChildElements(futureParentElement);
}
if (namespace !== 'html' && $compileNodes[0] !== lastCompileNode) {
namespaceAdaptedCompileNodes = jqLite(
var $linkNode;
if (namespace !== 'html') {
// When using a directive with replace:true and templateUrl the $compileNodes
// (or a child element inside of them)
// might change, so we need to recreate the namespace adapted compileNodes
// for call to the link function.
// Note: This will already clone the nodes...
$linkNode = jqLite(
wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
);
} else if (cloneConnectFn) {
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
$linkNode = JQLitePrototype.clone.call($compileNodes);
} else {
$linkNode = $compileNodes;
}
// When using a directive with replace:true and templateUrl the $compileNodes
// might change, so we need to recreate the namespace adapted compileNodes.
lastCompileNode = $compileNodes[0];
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
var $linkNode = cloneConnectFn
? JQLitePrototype.clone.call(namespaceAdaptedCompileNodes) // IMPORTANT!!!
: namespaceAdaptedCompileNodes;
if (transcludeControllers) {
for (var controllerName in transcludeControllers) {
@@ -1166,20 +1315,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) {
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement) {
var scopeCreated = false;
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
if (!transcludedScope) {
transcludedScope = scope.$new();
transcludedScope = scope.$new(false, containingScope);
transcludedScope.$$transcluded = true;
scopeCreated = true;
}
var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
if (scopeCreated && !elementTransclusion) {
clone.on('$destroy', function() { transcludedScope.$destroy(); });
}
return clone;
return transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, futureParentElement);
};
return boundTranscludeFn;
@@ -1202,7 +1345,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
className;
switch(nodeType) {
case 1: /* Element */
case NODE_TYPE_ELEMENT: /* Element */
// use the node name: <directive>
addDirective(directives,
directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective);
@@ -1214,37 +1357,35 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var attrEndName = false;
attr = nAttrs[j];
if (!msie || msie >= 8 || attr.specified) {
name = attr.name;
value = trim(attr.value);
name = attr.name;
value = trim(attr.value);
// support ngAttr attribute binding
ngAttrName = directiveNormalize(name);
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
name = snake_case(ngAttrName.substr(6), '-');
}
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
if (directiveIsMultiElement(directiveNName)) {
if (ngAttrName === directiveNName + 'Start') {
attrStartName = name;
attrEndName = name.substr(0, name.length - 5) + 'end';
name = name.substr(0, name.length - 6);
}
}
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
attrs[nName] = value;
if (getBooleanAttrName(node, nName)) {
attrs[nName] = true; // presence means true
}
}
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
attrEndName);
// support ngAttr attribute binding
ngAttrName = directiveNormalize(name);
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
name = snake_case(ngAttrName.substr(6), '-');
}
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
if (directiveIsMultiElement(directiveNName)) {
if (ngAttrName === directiveNName + 'Start') {
attrStartName = name;
attrEndName = name.substr(0, name.length - 5) + 'end';
name = name.substr(0, name.length - 6);
}
}
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
attrs[nName] = value;
if (getBooleanAttrName(node, nName)) {
attrs[nName] = true; // presence means true
}
}
addAttrInterpolateDirective(node, directives, value, nName, isNgAttr);
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
attrEndName);
}
// use class as directive
@@ -1259,10 +1400,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
break;
case 3: /* Text Node */
case NODE_TYPE_TEXT: /* Text Node */
addTextInterpolateDirective(directives, node.nodeValue);
break;
case 8: /* Comment */
case NODE_TYPE_COMMENT: /* Comment */
try {
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
if (match) {
@@ -1302,7 +1443,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
"Unterminated attribute, found '{0}' but no matching '{1}' found.",
attrStart, attrEnd);
}
if (node.nodeType == 1 /** Element **/) {
if (node.nodeType == NODE_TYPE_ELEMENT) {
if (node.hasAttribute(attrStart)) depth++;
if (node.hasAttribute(attrEnd)) depth--;
}
@@ -1481,11 +1622,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(directiveValue)) {
$template = [];
} else {
$template = jqLite(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
$template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
}
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
throw $compileMinErr('tplrt',
"Template for directive '{0}' must have exactly one root element. {1}",
directiveName, '');
@@ -1589,14 +1730,26 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function getControllers(directiveName, require, $element, elementControllers) {
var value, retrievalMethod = 'data', optional = false;
var $searchElement = $element;
var match;
if (isString(require)) {
while((value = require.charAt(0)) == '^' || value == '?') {
require = require.substr(1);
if (value == '^') {
retrievalMethod = 'inheritedData';
}
optional = optional || value == '?';
match = require.match(REQUIRE_PREFIX_REGEXP);
require = require.substring(match[0].length);
if (match[3]) {
if (match[1]) match[3] = null;
else match[1] = match[3];
}
if (match[1] === '^') {
retrievalMethod = 'inheritedData';
} else if (match[1] === '^^') {
retrievalMethod = 'inheritedData';
$searchElement = $element.parent();
}
if (match[2] === '?') {
optional = true;
}
value = null;
if (elementControllers && retrievalMethod === 'data') {
@@ -1604,7 +1757,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
value = value.instance;
}
}
value = value || $element[retrievalMethod]('$' + require + 'Controller');
value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
if (!value && !optional) {
throw $compileMinErr('ctreq',
@@ -1810,7 +1963,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (!futureParentElement) {
futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element;
}
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement);
return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild);
}
}
}
@@ -1951,11 +2104,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(content)) {
$template = [];
} else {
$template = jqLite(wrapTemplate(templateNamespace, trim(content)));
$template = removeComments(wrapTemplate(templateNamespace, trim(content)));
}
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) {
throw $compileMinErr('tplrt',
"Template for directive '{0}' must have exactly one root element. {1}",
origAsyncDirective.name, templateUrl);
@@ -1994,6 +2147,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
boundTranscludeFn = linkQueue.shift(),
linkNode = $compileNode[0];
if (scope.$$destroyed) continue;
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
var oldClasses = beforeTemplateLinkNode.className;
@@ -2020,6 +2175,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
var childBoundTranscludeFn = boundTranscludeFn;
if (scope.$$destroyed) return;
if (linkQueue) {
linkQueue.push(scope);
linkQueue.push(node);
@@ -2136,6 +2292,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
"ng- versions (such as ng-click instead of onclick) instead.");
}
// If the attribute was removed, then we are done
if (!attr[name]) {
return;
}
// we need to interpolate again, in case the attribute value has been updated
// (e.g. by another directive's compile function)
interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name),
@@ -2362,3 +2523,20 @@ function tokenDifference(str1, str2) {
}
return values;
}
function removeComments(jqNodes) {
jqNodes = jqLite(jqNodes);
var i = jqNodes.length;
if (i <= 1) {
return jqNodes;
}
while (i--) {
var node = jqNodes[i];
if (node.nodeType === NODE_TYPE_COMMENT) {
splice.call(jqNodes, i, 1);
}
}
return jqNodes;
}
-16
View File
@@ -16,22 +16,6 @@
var htmlAnchorDirective = valueFn({
restrict: 'E',
compile: function(element, attr) {
if (msie <= 8) {
// turn <a href ng-click="..">link</a> into a stylable link in IE
// but only if it doesn't have name attribute, in which case it's an anchor
if (!attr.href && !attr.name) {
attr.$set('href', '');
}
// add a comment node to anchors to workaround IE bug that causes element content to be reset
// to new attribute content if attribute is updated with value containing @ and element also
// contains value with @
// see issue #1949
element.append(document.createComment('IE fix'));
}
if (!attr.href && !attr.xlinkHref && !attr.name) {
return function(scope, element) {
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
+58 -16
View File
@@ -4,16 +4,19 @@
*/
var nullFormCtrl = {
$addControl: noop,
$$renameControl: nullFormRenameControl,
$removeControl: noop,
$setValidity: noop,
$$setPending: noop,
$setDirty: noop,
$setPristine: noop,
$setSubmitted: noop,
$$clearControlValidity: noop
$setSubmitted: noop
},
SUBMITTED_CLASS = 'ng-submitted';
function nullFormRenameControl(control, name) {
control.$name = name;
}
/**
* @ngdoc type
* @name form.FormController
@@ -51,17 +54,18 @@ SUBMITTED_CLASS = 'ng-submitted';
*
*/
//asks for $scope to fool the BC controller module
FormController.$inject = ['$element', '$attrs', '$scope', '$animate'];
function FormController(element, attrs, $scope, $animate) {
FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
function FormController(element, attrs, $scope, $animate, $interpolate) {
var form = this,
parentForm = element.parent().controller('form') || nullFormCtrl,
controls = [];
var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;
// init state
form.$error = {};
form.$$success = {};
form.$pending = undefined;
form.$name = attrs.name || attrs.ngForm;
form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
form.$dirty = false;
form.$pristine = true;
form.$valid = true;
@@ -70,9 +74,6 @@ function FormController(element, attrs, $scope, $animate) {
parentForm.$addControl(form);
// Setup initial state of the control
element.addClass(PRISTINE_CLASS);
/**
* @ngdoc method
* @name form.FormController#$rollbackViewValue
@@ -127,6 +128,17 @@ function FormController(element, attrs, $scope, $animate) {
}
};
// Private API: rename a form control
form.$$renameControl = function(control, newName) {
var oldName = control.$name;
if (form[oldName] === control) {
delete form[oldName];
}
form[newName] = control;
control.$name = newName;
};
/**
* @ngdoc method
* @name form.FormController#$removeControl
@@ -230,6 +242,25 @@ function FormController(element, attrs, $scope, $animate) {
});
};
/**
* @ngdoc method
* @name form.FormController#$setUntouched
*
* @description
* Sets the form to its untouched state.
*
* This method can be called to remove the 'ng-touched' class and set the form controls to their
* untouched state (ng-untouched class).
*
* Setting a form controls back to their untouched state is often useful when setting the form
* back to its pristine state.
*/
form.$setUntouched = function () {
forEach(controls, function(control) {
control.$setUntouched();
});
};
/**
* @ngdoc method
* @name form.FormController#$setSubmitted
@@ -415,10 +446,14 @@ var formDirectiveFactory = function(isNgForm) {
name: 'form',
restrict: isNgForm ? 'EAC' : 'E',
controller: FormController,
compile: function() {
compile: function ngFormCompile(formElement) {
// Setup initial state of the control
formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
return {
pre: function(scope, formElement, attr, controller) {
if (!attr.action) {
pre: function ngFormPreLink(scope, formElement, attr, controller) {
// if `action` attr is not present on the form, prevent the default action (submission)
if (!('action' in attr)) {
// we can't use jq events because if a form is destroyed during submission the default
// action is not prevented. see #1238
//
@@ -447,13 +482,20 @@ var formDirectiveFactory = function(isNgForm) {
});
}
var parentFormCtrl = formElement.parent().controller('form'),
alias = attr.name || attr.ngForm;
var parentFormCtrl = controller.$$parentForm,
alias = controller.$name;
if (alias) {
setter(scope, alias, controller, alias);
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
if (alias === newValue) return;
setter(scope, alias, undefined, alias);
alias = newValue;
setter(scope, alias, controller, alias);
parentFormCtrl.$$renameControl(controller, alias);
});
}
if (parentFormCtrl) {
if (parentFormCtrl !== nullFormCtrl) {
formElement.on('$destroy', function() {
parentFormCtrl.$removeControl(controller);
if (alias) {
+90 -60
View File
@@ -14,10 +14,10 @@ var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d))?$/;
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d))?$/;
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
var $ngModelMinErr = new minErr('ngModel');
@@ -281,8 +281,8 @@ var inputType = {
</example>
*/
'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP,
createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss']),
'yyyy-MM-ddTHH:mm:ss'),
createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']),
'yyyy-MM-ddTHH:mm:ss.sss'),
/**
* @ngdoc input
@@ -370,8 +370,8 @@ var inputType = {
</example>
*/
'time': createDateInputType('time', TIME_REGEXP,
createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss']),
'HH:mm:ss'),
createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']),
'HH:mm:ss.sss'),
/**
* @ngdoc input
@@ -1067,7 +1067,7 @@ function createDateParser(regexp, mapping) {
HH: date.getHours(),
mm: date.getMinutes(),
ss: date.getSeconds(),
sss: date.getMilliseconds()
sss: date.getMilliseconds() / 1000
};
} else {
map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 };
@@ -1078,7 +1078,7 @@ function createDateParser(regexp, mapping) {
map[mapping[index]] = +part;
}
});
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss || 0);
return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0);
}
}
@@ -1091,16 +1091,15 @@ function createDateInputType(type, regexp, parseDate, format) {
badInputChecker(scope, element, attr, ctrl);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;
var previousDate;
ctrl.$$parserName = type;
ctrl.$parsers.push(function(value) {
if (ctrl.$isEmpty(value)) return null;
if (regexp.test(value)) {
var previousDate = ctrl.$modelValue;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
previousDate = new Date(previousDate.getTime() + timezoneOffset);
}
// Note: We cannot read ctrl.$modelValue, as there might be a different
// parser/formatter in the processing chain so that the model
// contains some different data format!
var parsedDate = parseDate(value, previousDate);
if (timezone === 'UTC') {
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
@@ -1111,8 +1110,18 @@ function createDateInputType(type, regexp, parseDate, format) {
});
ctrl.$formatters.push(function(value) {
if (isDate(value)) {
if (!ctrl.$isEmpty(value)) {
if (!isDate(value)) {
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
}
previousDate = value;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
previousDate = new Date(previousDate.getTime() + timezoneOffset);
}
return $filter('date')(value, format, timezone);
} else {
previousDate = null;
}
return '';
});
@@ -1138,6 +1147,11 @@ function createDateInputType(type, regexp, parseDate, format) {
ctrl.$validate();
});
}
// Override the standard $isEmpty to detect invalid dates as well
ctrl.$isEmpty = function(value) {
// Invalid Date: getTime() returns NaN
return !value || (value.getTime && value.getTime() !== value.getTime());
};
function parseObservedDateValue(val) {
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
@@ -1452,10 +1466,12 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
return {
restrict: 'E',
require: ['?ngModel'],
link: function(scope, element, attr, ctrls) {
if (ctrls[0]) {
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
$browser, $filter, $parse);
link: {
pre: function(scope, element, attr, ctrls) {
if (ctrls[0]) {
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
$browser, $filter, $parse);
}
}
}
};
@@ -1657,8 +1673,8 @@ var VALID_CLASS = 'ng-valid',
*
*
*/
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q',
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q) {
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
this.$viewValue = Number.NaN;
this.$modelValue = Number.NaN;
this.$validators = {};
@@ -1675,7 +1691,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
this.$error = {}; // keep invalid keys here
this.$$success = {}; // keep valid keys here
this.$pending = undefined; // keep pending keys here
this.$name = $attr.name;
this.$name = $interpolate($attr.name || '', false)($scope);
var parsedNgModel = $parse($attr.ngModel),
@@ -1756,11 +1772,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
currentValidationRunId = 0;
// Setup initial state of the control
$element
.addClass(PRISTINE_CLASS)
.addClass(UNTOUCHED_CLASS);
/**
* @ngdoc method
* @name ngModel.NgModelController#$setValidity
@@ -2053,14 +2064,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
};
this.$$parseAndValidate = function() {
var parserValid = true,
viewValue = ctrl.$$lastCommittedViewValue,
modelValue = viewValue;
for(var i = 0; i < ctrl.$parsers.length; i++) {
modelValue = ctrl.$parsers[i](modelValue);
if (isUndefined(modelValue)) {
parserValid = false;
break;
var viewValue = ctrl.$$lastCommittedViewValue;
var modelValue = viewValue;
var parserValid = isUndefined(modelValue) ? undefined : true;
if (parserValid) {
for(var i = 0; i < ctrl.$parsers.length; i++) {
modelValue = ctrl.$parsers[i](modelValue);
if (isUndefined(modelValue)) {
parserValid = false;
break;
}
}
}
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
@@ -2377,36 +2391,51 @@ var ngModelDirective = function() {
restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'],
controller: NgModelController,
link: {
pre: function(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0],
formCtrl = ctrls[1] || nullFormCtrl;
// Prelink needs to run before any input directive
// so that we can set the NgModelOptions in NgModelController
// before anyone else uses it.
priority: 1,
compile: function ngModelCompile(element) {
// Setup initial state of the control
element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
return {
pre: function ngModelPreLink(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0],
formCtrl = ctrls[1] || nullFormCtrl;
// notify others, especially parent forms
formCtrl.$addControl(modelCtrl);
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
scope.$on('$destroy', function() {
formCtrl.$removeControl(modelCtrl);
});
},
post: function(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0];
if (modelCtrl.$options && modelCtrl.$options.updateOn) {
element.on(modelCtrl.$options.updateOn, function(ev) {
modelCtrl.$$debounceViewValueCommit(ev && ev.type);
// notify others, especially parent forms
formCtrl.$addControl(modelCtrl);
attr.$observe('name', function(newValue) {
if (modelCtrl.$name !== newValue) {
formCtrl.$$renameControl(modelCtrl, newValue);
}
});
scope.$on('$destroy', function() {
formCtrl.$removeControl(modelCtrl);
});
},
post: function ngModelPostLink(scope, element, attr, ctrls) {
var modelCtrl = ctrls[0];
if (modelCtrl.$options && modelCtrl.$options.updateOn) {
element.on(modelCtrl.$options.updateOn, function(ev) {
modelCtrl.$$debounceViewValueCommit(ev && ev.type);
});
}
element.on('blur', function(ev) {
if (modelCtrl.$touched) return;
scope.$apply(function() {
modelCtrl.$setTouched();
});
});
}
element.on('blur', function(ev) {
if (modelCtrl.$touched) return;
scope.$apply(function() {
modelCtrl.$setTouched();
});
});
}
};
}
};
};
@@ -2961,8 +2990,9 @@ function addSetValidityMethod(context) {
parentForm = context.parentForm,
$animate = context.$animate;
classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
ctrl.$setValidity = setValidity;
toggleValidationCss('', true);
function setValidity(validationErrorKey, state, options) {
if (state === undefined) {
+8 -6
View File
@@ -58,11 +58,9 @@ var ngBindDirective = ['$compile', function($compile) {
$compile.$$addBindingClass(templateElement);
return function ngBindLink(scope, element, attr) {
$compile.$$addBindingInfo(element, attr.ngBind);
element = element[0];
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
// We are purposefully using == here rather than === because we want to
// catch when value is "null or undefined"
// jshint -W041
element.text(value == undefined ? '' : value);
element.textContent = value === undefined ? '' : value;
});
};
}
@@ -128,8 +126,9 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
return function ngBindTemplateLink(scope, element, attr) {
var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
$compile.$$addBindingInfo(element, interpolateFn.expressions);
element = element[0];
attr.$observe('ngBindTemplate', function(value) {
element.text(value);
element.textContent = value === undefined ? '' : value;
});
};
}
@@ -146,7 +145,10 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
* element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link
* ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize`
* is available, for example, by including {@link ngSanitize} in your module's dependencies (not in
* core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to
* core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to
* include "angular-sanitize.js" in your application.
*
* You may also bypass sanitization for values you know are safe. To do so, bind to
* an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
* under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}.
*
+1
View File
@@ -23,6 +23,7 @@
*
* @element ANY
* @scope
* @priority 500
* @param {expression} ngController Name of a constructor function registered with the current
* {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression}
* that on the current scope evaluates to a constructor function.
+119 -1
View File
@@ -49,7 +49,125 @@
...
</html>
```
*/
* @example
// Note: the suffix `.csp` in the example name triggers
// csp mode in our http server!
<example name="example.csp" module="cspExample" ng-csp="true">
<file name="index.html">
<div ng-controller="MainController as ctrl">
<div>
<button ng-click="ctrl.inc()" id="inc">Increment</button>
<span id="counter">
{{ctrl.counter}}
</span>
</div>
<div>
<button ng-click="ctrl.evil()" id="evil">Evil</button>
<span id="evilError">
{{ctrl.evilError}}
</span>
</div>
</div>
</file>
<file name="script.js">
angular.module('cspExample', [])
.controller('MainController', function() {
this.counter = 0;
this.inc = function() {
this.counter++;
};
this.evil = function() {
// jshint evil:true
try {
eval('1+2');
} catch (e) {
this.evilError = e.message;
}
};
});
</file>
<file name="protractor.js" type="protractor">
var util, webdriver;
var incBtn = element(by.id('inc'));
var counter = element(by.id('counter'));
var evilBtn = element(by.id('evil'));
var evilError = element(by.id('evilError'));
function getAndClearSevereErrors() {
return browser.manage().logs().get('browser').then(function(browserLog) {
return browserLog.filter(function(logEntry) {
return logEntry.level.value > webdriver.logging.Level.WARNING.value;
});
});
}
function clearErrors() {
getAndClearSevereErrors();
}
function expectNoErrors() {
getAndClearSevereErrors().then(function(filteredLog) {
expect(filteredLog.length).toEqual(0);
if (filteredLog.length) {
console.log('browser console errors: ' + util.inspect(filteredLog));
}
});
}
function expectError(regex) {
getAndClearSevereErrors().then(function(filteredLog) {
var found = false;
filteredLog.forEach(function(log) {
if (log.message.match(regex)) {
found = true;
}
});
if (!found) {
throw new Error('expected an error that matches ' + regex);
}
});
}
beforeEach(function() {
util = require('util');
webdriver = require('protractor/node_modules/selenium-webdriver');
});
// For now, we only test on Chrome,
// as Safari does not load the page with Protractor's injected scripts,
// and Firefox webdriver always disables content security policy (#6358)
if (browser.params.browser !== 'chrome') {
return;
}
it('should not report errors when the page is loaded', function() {
// clear errors so we are not dependent on previous tests
clearErrors();
// Need to reload the page as the page is already loaded when
// we come here
browser.driver.getCurrentUrl().then(function(url) {
browser.get(url);
});
expectNoErrors();
});
it('should evaluate expressions', function() {
expect(counter.getText()).toEqual('0');
incBtn.click();
expect(counter.getText()).toEqual('1');
expectNoErrors();
});
it('should throw and report an error when using "eval"', function() {
evilBtn.click();
expect(evilError.getText()).toMatch(/Content Security Policy/);
expectError(/Content Security Policy/);
});
</file>
</example>
*/
// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
// bootstrap the system (before $parse is instantiated), for this reason we just have
+3 -3
View File
@@ -19,7 +19,7 @@
* Note that when an element is removed using `ngIf` its scope is destroyed and a new scope
* is created when the element is restored. The scope created within `ngIf` inherits from
* its parent scope using
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance).
* [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance).
* An important implication of this is if `ngModel` is used within `ngIf` to bind to
* a javascript primitive defined in the parent scope. In this case any modifications made to the
* variable within the child scope will override (hide) the value in the parent scope.
@@ -33,8 +33,8 @@
* and `leave` effects.
*
* @animations
* enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container
* leave - happens just before the ngIf contents are removed from the DOM
* enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
* leave - happens just before the `ngIf` contents are removed from the DOM
*
* @element ANY
* @scope
+10 -2
View File
@@ -1,5 +1,7 @@
'use strict';
var NG_HIDE_CLASS = 'ng-hide';
var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
/**
* @ngdoc directive
* @name ngShow
@@ -161,7 +163,11 @@ var ngShowDirective = ['$animate', function($animate) {
multiElement: true,
link: function(scope, element, attr) {
scope.$watch(attr.ngShow, function ngShowWatchAction(value){
$animate[value ? 'removeClass' : 'addClass'](element, 'ng-hide');
// we're adding a temporary, animation-specific class for ng-hide since this way
// we can control when the element is actually displayed on screen without having
// to have a global/greedy CSS selector that breaks when other animations are run.
// Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845
$animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
});
}
};
@@ -316,7 +322,9 @@ var ngHideDirective = ['$animate', function($animate) {
multiElement: true,
link: function(scope, element, attr) {
scope.$watch(attr.ngHide, function ngHideWatchAction(value){
$animate[value ? 'addClass' : 'removeClass'](element, 'ng-hide');
// The comment inside of the ngShowDirective explains why we add and
// remove a temporary class for the show/hide animation
$animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, NG_HIDE_IN_PROGRESS_CLASS);
});
}
};
+150 -123
View File
@@ -35,6 +35,12 @@ var ngOptionsMinErr = minErr('ngOptions');
* be bound to string values at present.
* </div>
*
* <div class="alert alert-info">
* **Note:** Using `select as` will bind the result of the `select as` expression to the model, but
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
* or property name (for object data sources) of the value within the collection.
* </div>
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} required The control is considered valid only if value is entered.
@@ -69,7 +75,25 @@ var ngOptionsMinErr = minErr('ngOptions');
* DOM element.
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
* `value` variable (e.g. `value.propertyName`).
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
* even when the options are recreated (e.g. reloaded from the server).
* <div class="alert alert-info">
* **Note:** Using `select as` together with `trackexpr` is not possible (and will throw).
* Reasoning:
* - Example: <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItemß'}}],
* $scope.selected = {name: 'aSubItem'};
* - track by is always applied to `value`, with purpose to preserve the selection,
* (to `item` in this case)
* - to calculate whether an item is selected we do the following:
* 1. apply `track by` to the values in the array, e.g.
* In the example: [1,2]
* 2. apply `track by` to the already selected value in `ngModel`:
* In the example: this is not possible, as `track by` refers to `item.id`, but the selected
* value from `ngModel` is `{name: aSubItem}`.
*
* </div>
*
* @example
<example module="selectExample">
@@ -326,6 +350,8 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
var displayFn = $parse(match[2] || match[1]),
valueName = match[4] || match[6],
selectAs = / as /.test(match[0]) && match[1],
selectAsFn = selectAs ? $parse(selectAs) : null,
keyName = match[5],
groupByFn = $parse(match[3] || ''),
valueFn = $parse(match[2] ? match[1] : valueName),
@@ -336,7 +362,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// We try to reuse these if possible
// - optionGroupsCache[0] is the options with no option group
// - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
optionGroupsCache = [[{element: selectElement, label:''}]];
optionGroupsCache = [[{element: selectElement, label:''}]],
//re-usable object to represent option's locals
locals = {};
if (trackFn && selectAsFn) {
throw ngOptionsMinErr('trkslct',
"Comprehension expression cannot contain both selectAs '{0}' " +
"and trackBy '{1}' expressions.",
selectAs, track);
}
if (nullOption) {
// compile the element since there might be bindings in it
@@ -354,103 +389,110 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// clear contents, we'll add what's needed based on the model
selectElement.empty();
selectElement.on('change', function() {
scope.$apply(function() {
var optionGroup,
collection = valuesFn(scope) || [],
locals = {},
key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
if (multiple) {
value = [];
for (groupIndex = 0, groupLength = optionGroupsCache.length;
groupIndex < groupLength;
groupIndex++) {
// list of options for that group. (first item has the parent)
optionGroup = optionGroupsCache[groupIndex];
for(index = 1, length = optionGroup.length; index < length; index++) {
if ((optionElement = optionGroup[index].element)[0].selected) {
key = optionElement.val();
if (keyName) locals[keyName] = key;
if (trackFn) {
for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
locals[valueName] = collection[trackIndex];
if (trackFn(scope, locals) == key) break;
}
} else {
locals[valueName] = collection[key];
}
value.push(valueFn(scope, locals));
}
}
}
} else {
key = selectElement.val();
if (key == '?') {
value = undefined;
} else if (key === ''){
value = null;
} else {
if (trackFn) {
for (trackIndex = 0; trackIndex < collection.length; trackIndex++) {
locals[valueName] = collection[trackIndex];
if (trackFn(scope, locals) == key) {
value = valueFn(scope, locals);
break;
}
}
} else {
locals[valueName] = collection[key];
if (keyName) locals[keyName] = key;
value = valueFn(scope, locals);
}
}
}
ctrl.$setViewValue(value);
render();
});
});
selectElement.on('change', selectionChanged);
ctrl.$render = render;
scope.$watchCollection(valuesFn, scheduleRendering);
scope.$watchCollection(function () {
var locals = {},
values = valuesFn(scope);
if (values) {
var toDisplay = new Array(values.length);
for (var i = 0, ii = values.length; i < ii; i++) {
locals[valueName] = values[i];
toDisplay[i] = displayFn(scope, locals);
}
return toDisplay;
}
}, scheduleRendering);
scope.$watchCollection(getLabels, scheduleRendering);
if (multiple) {
scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
}
// ------------------------------------------------------------------ //
function getSelectedSet() {
var selectedSet = false;
if (multiple) {
var modelValue = ctrl.$modelValue;
if (trackFn && isArray(modelValue)) {
selectedSet = new HashMap([]);
var locals = {};
for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) {
locals[valueName] = modelValue[trackIndex];
selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]);
}
} else {
selectedSet = new HashMap(modelValue);
}
}
return selectedSet;
function callExpression(exprFn, key, value) {
locals[valueName] = value;
if (keyName) locals[keyName] = key;
return exprFn(scope, locals);
}
function selectionChanged() {
scope.$apply(function() {
var optionGroup,
collection = valuesFn(scope) || [],
key, value, optionElement, index, groupIndex, length, groupLength, trackIndex;
var viewValue;
if (multiple) {
viewValue = [];
forEach(selectElement.val(), function(selectedKey) {
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
});
} else {
var selectedKey = selectElement.val();
viewValue = getViewValue(selectedKey, collection[selectedKey]);
}
ctrl.$setViewValue(viewValue);
render();
});
}
function getViewValue(key, value) {
if (key === '?') {
return undefined;
} else if (key === '') {
return null;
} else {
var viewValueFn = selectAsFn ? selectAsFn : valueFn;
return callExpression(viewValueFn, key, value);
}
}
function getLabels() {
var values = valuesFn(scope);
var toDisplay;
if (values && isArray(values)) {
toDisplay = new Array(values.length);
for (var i = 0, ii = values.length; i < ii; i++) {
toDisplay[i] = callExpression(displayFn, i, values[i]);
}
return toDisplay;
} else if (values) {
// TODO: Add a test for this case
toDisplay = {};
for (var prop in values) {
if (values.hasOwnProperty(prop)) {
toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
}
}
}
return toDisplay;
}
function createIsSelectedFn(viewValue) {
var selectedSet;
if (multiple) {
if (!selectAs && trackFn && isArray(viewValue)) {
selectedSet = new HashMap([]);
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
// tracking by key
selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
}
} else {
selectedSet = new HashMap(viewValue);
}
} else if (!selectAsFn && trackFn) {
viewValue = callExpression(trackFn, null, viewValue);
}
return function isSelected(key, value) {
var compareValueFn;
if (selectAsFn) {
compareValueFn = selectAsFn;
} else if (trackFn) {
compareValueFn = trackFn;
} else {
compareValueFn = valueFn;
}
if (multiple) {
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
} else {
return viewValue == callExpression(compareValueFn, key, value);
}
};
}
function scheduleRendering() {
if (!renderScheduled) {
@@ -459,78 +501,64 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
}
}
function render() {
renderScheduled = false;
// Temporary location for the option groups before we render them
// Temporary location for the option groups before we render them
var optionGroups = {'':[]},
optionGroupNames = [''],
optionGroupName,
optionGroup,
option,
existingParent, existingOptions, existingOption,
modelValue = ctrl.$modelValue,
viewValue = ctrl.$viewValue,
values = valuesFn(scope) || [],
keys = keyName ? sortedKeys(values) : values,
key,
value,
groupLength, length,
groupIndex, index,
locals = {},
selected,
selectedSet = getSelectedSet(),
isSelected = createIsSelectedFn(viewValue),
anySelected = false,
lastElement,
element,
label;
// We now build up the list of options we need (we merge later)
for (index = 0; length = keys.length, index < length; index++) {
key = index;
if (keyName) {
key = keys[index];
if ( key.charAt(0) === '$' ) continue;
locals[keyName] = key;
}
value = values[key];
locals[valueName] = values[key];
optionGroupName = groupByFn(scope, locals) || '';
optionGroupName = callExpression(groupByFn, key, value) || '';
if (!(optionGroup = optionGroups[optionGroupName])) {
optionGroup = optionGroups[optionGroupName] = [];
optionGroupNames.push(optionGroupName);
}
if (multiple) {
selected = isDefined(
selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals))
);
} else {
if (trackFn) {
var modelCast = {};
modelCast[valueName] = modelValue;
selected = trackFn(scope, modelCast) === trackFn(scope, locals);
} else {
selected = modelValue === valueFn(scope, locals);
}
selectedSet = selectedSet || selected; // see if at least one item is selected
}
label = displayFn(scope, locals); // what will be seen by the user
selected = isSelected(key, value);
anySelected = anySelected || selected;
label = callExpression(displayFn, key, value); // what will be seen by the user
// doing displayFn(scope, locals) || '' overwrites zero values
label = isDefined(label) ? label : '';
optionGroup.push({
// either the index into array or key from object
id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index),
id: (keyName ? keys[index] : index),
label: label,
selected: selected // determine if we should be selected
});
}
if (!multiple) {
if (nullOption || modelValue === null) {
if (nullOption || viewValue === null) {
// insert null option if we have a placeholder, or the model is null
optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
} else if (!selectedSet) {
optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
} else if (!anySelected) {
// option could not be found, we have to insert the undefined item
optionGroups[''].unshift({id:'?', label:'', selected:true});
}
@@ -611,6 +639,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
id: option.id,
selected: option.selected
});
selectCtrl.addOption(option.label, element);
if (lastElement) {
lastElement.after(element);
} else {
@@ -622,7 +651,9 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// remove any excessive OPTIONs in a group
index++; // increment since the existingOptions[0] is parent element not OPTION
while(existingOptions.length > index) {
existingOptions.pop().element.remove();
option = existingOptions.pop();
selectCtrl.removeOption(option.label);
option.element.remove();
}
}
// remove any excessive OPTGROUPs from select
@@ -658,11 +689,7 @@ var optionDirective = ['$interpolate', function($interpolate) {
selectCtrl = parent.data(selectCtrlName) ||
parent.parent().data(selectCtrlName); // in case we are in optgroup
if (selectCtrl && selectCtrl.databound) {
// For some reason Opera defaults to true and if not overridden this messes up the repeater.
// We don't want the view to drive the initialization of the model anyway.
element.prop('selected', false);
} else {
if (!selectCtrl || !selectCtrl.databound) {
selectCtrl = nullSelectCtrl;
}
+15 -14
View File
@@ -34,9 +34,9 @@
}]);
</script>
<div ng-controller="ExampleController">
Limit {{numbers}} to: <input type="integer" ng-model="numLimit">
Limit {{numbers}} to: <input type="number" step="1" ng-model="numLimit">
<p>Output numbers: {{ numbers | limitTo:numLimit }}</p>
Limit {{letters}} to: <input type="integer" ng-model="letterLimit">
Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit">
<p>Output letters: {{ letters | limitTo:letterLimit }}</p>
Limit {{longNumber}} to: <input type="integer" ng-model="longNumberLimit">
<p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p>
@@ -59,17 +59,18 @@
expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
});
it('should update the output when -3 is entered', function() {
numLimitInput.clear();
numLimitInput.sendKeys('-3');
letterLimitInput.clear();
letterLimitInput.sendKeys('-3');
longNumberLimitInput.clear();
longNumberLimitInput.sendKeys('-3');
expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
expect(limitedLetters.getText()).toEqual('Output letters: ghi');
expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
});
// There is a bug in safari and protractor that doesn't like the minus key
// it('should update the output when -3 is entered', function() {
// numLimitInput.clear();
// numLimitInput.sendKeys('-3');
// letterLimitInput.clear();
// letterLimitInput.sendKeys('-3');
// longNumberLimitInput.clear();
// longNumberLimitInput.sendKeys('-3');
// expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
// expect(limitedLetters.getText()).toEqual('Output letters: ghi');
// expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
// });
it('should not exceed the maximum size of input array', function() {
numLimitInput.clear();
@@ -84,7 +85,7 @@
});
</file>
</example>
*/
*/
function limitToFilter(){
return function(input, limit) {
if (isNumber(input)) input = input.toString();
+13 -4
View File
@@ -11,7 +11,7 @@
* correctly, make sure they are actually being saved as numbers and not strings.
*
* @param {Array} array The array to sort.
* @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
* used by the comparator to determine the order of elements.
*
* Can be one of:
@@ -24,10 +24,13 @@
* is interpreted as a property name to be used in comparisons (for example `"special name"`
* to sort object by the value of their `special name` property). An expression can be
* optionally prefixed with `+` or `-` to control ascending or descending sort order
* (for example, `+name` or `-name`).
* (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
* element itself is used to compare where sorting.
* - `Array`: An array of function or string predicates. The first predicate in the array
* is used for sorting, but when two items are equivalent, the next predicate is used.
*
* If the predicate is missing or empty then it defaults to `'+'`.
*
* @param {boolean=} reverse Reverse the order of the array.
* @returns {Array} Sorted copy of the source array.
*
@@ -116,15 +119,21 @@ orderByFilter.$inject = ['$parse'];
function orderByFilter($parse){
return function(array, sortPredicate, reverseOrder) {
if (!(isArrayLike(array))) return array;
if (!sortPredicate) return array;
sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
sortPredicate = map(sortPredicate, function(predicate){
if (sortPredicate.length === 0) { sortPredicate = ['+']; }
sortPredicate = sortPredicate.map(function(predicate){
var descending = false, get = predicate || identity;
if (isString(predicate)) {
if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
descending = predicate.charAt(0) == '-';
predicate = predicate.substring(1);
}
if ( predicate === '' ) {
// Effectively no predicate was passed so we compare identity
return reverseComparator(function(a,b) {
return compare(a, b);
}, descending);
}
get = $parse(predicate);
if (get.constant) {
var key = get();
+14 -9
View File
@@ -89,7 +89,8 @@ function $HttpProvider() {
var JSON_START = /^\s*(\[|\{[^\{])/,
JSON_END = /[\}\]]\s*$/,
PROTECTION_PREFIX = /^\)\]\}',?\n/,
CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
APPLICATION_JSON = 'application/json',
CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
/**
* @ngdoc property
@@ -114,12 +115,15 @@ function $HttpProvider() {
**/
var defaults = this.defaults = {
// transform incoming response data
transformResponse: [function(data) {
transformResponse: [function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
// strip json vulnerability protection prefix
data = data.replace(PROTECTION_PREFIX, '');
if (JSON_START.test(data) && JSON_END.test(data))
var contentType = headers('Content-Type');
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
(JSON_START.test(data) && JSON_END.test(data))) {
data = fromJson(data);
}
}
return data;
}],
@@ -662,12 +666,13 @@ function $HttpProvider() {
expect(data.getText()).toMatch(/Hello, \$http!/);
});
it('should make a JSONP request to angularjs.org', function() {
sampleJsonpBtn.click();
fetchBtn.click();
expect(status.getText()).toMatch('200');
expect(data.getText()).toMatch(/Super Hero!/);
});
// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
// it('should make a JSONP request to angularjs.org', function() {
// sampleJsonpBtn.click();
// fetchBtn.click();
// expect(status.getText()).toMatch('200');
// expect(data.getText()).toMatch(/Super Hero!/);
// });
it('should make JSONP request to invalid URL and invoke the error handler',
function() {
+30 -60
View File
@@ -1,17 +1,7 @@
'use strict';
function createXhr(method) {
//if IE and the method is not RFC2616 compliant, or if XMLHttpRequest
//is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest
//if it is available
if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) ||
!window.XMLHttpRequest)) {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} else if (window.XMLHttpRequest) {
return new window.XMLHttpRequest();
}
throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
function createXhr() {
return new window.XMLHttpRequest();
}
/**
@@ -37,11 +27,8 @@ function $HttpBackendProvider() {
}
function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
var ABORTED = -1;
// TODO(vojta): fix the signature
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
var status;
$browser.$$incOutstandingRequestCount();
url = url || $browser.url();
@@ -59,7 +46,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
});
} else {
var xhr = createXhr(method);
var xhr = createXhr();
xhr.open(method, url, true);
forEach(headers, function(value, key) {
@@ -68,44 +55,39 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
}
});
// In IE6 and 7, this might be called synchronously when xhr.send below is called and the
// response is in the cache. the promise api will ensure that to the app code the api is
// always async
xhr.onreadystatechange = function() {
// onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by
// xhrs that are resolved while the app is in the background (see #5426).
// since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before
// continuing
//
// we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and
// Safari respectively.
if (xhr && xhr.readyState == 4) {
var responseHeaders = null,
response = null,
statusText = '';
xhr.onload = function requestLoaded() {
var statusText = xhr.statusText || '';
if(status !== ABORTED) {
responseHeaders = xhr.getAllResponseHeaders();
// 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)
var response = ('response' in xhr) ? 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)
response = ('response' in xhr) ? xhr.response : xhr.responseText;
}
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
var status = xhr.status === 1223 ? 204 : xhr.status;
// Accessing statusText on an aborted xhr object will
// throw an 'c00c023f error' in IE9 and lower, don't touch it.
if (!(status === ABORTED && msie < 10)) {
statusText = xhr.statusText;
}
completeRequest(callback,
status || xhr.status,
response,
responseHeaders,
statusText);
// fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache.
if (status === 0) {
status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
}
completeRequest(callback,
status,
response,
xhr.getAllResponseHeaders(),
statusText);
};
var requestError = function () {
// The response is always empty
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
completeRequest(callback, -1, null, null, '');
};
xhr.onerror = requestError;
xhr.onabort = requestError;
if (withCredentials) {
xhr.withCredentials = true;
}
@@ -138,7 +120,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
function timeoutRequest() {
status = ABORTED;
jsonpDone && jsonpDone();
xhr && xhr.abort();
}
@@ -148,17 +129,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
timeoutId && $browserDefer.cancel(timeoutId);
jsonpDone = xhr = null;
// fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache.
if (status === 0) {
status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
}
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
status = status === 1223 ? 204 : status;
statusText = statusText || '';
callback(status, response, headersString, statusText);
$browser.$$completeOutstandingRequest(noop);
}
+157 -41
View File
@@ -303,9 +303,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
}
LocationHashbangInHtml5Url.prototype =
LocationHashbangUrl.prototype =
LocationHtml5Url.prototype = {
var locationPrototype = {
/**
* Are we in html5 mode?
@@ -314,7 +312,7 @@ LocationHashbangInHtml5Url.prototype =
$$html5: false,
/**
* Has any change been replacing ?
* Has any change been replacing?
* @private
*/
$$replace: false,
@@ -416,7 +414,7 @@ LocationHashbangInHtml5Url.prototype =
* @return {string} path
*/
path: locationGetterSetter('$$path', function(path) {
path = path ? path.toString() : '';
path = path !== null ? path.toString() : '';
return path.charAt(0) == '/' ? path : '/' + path;
}),
@@ -513,7 +511,7 @@ LocationHashbangInHtml5Url.prototype =
* @return {string} hash
*/
hash: locationGetterSetter('$$hash', function(hash) {
return hash ? hash.toString() : '';
return hash !== null ? hash.toString() : '';
}),
/**
@@ -530,6 +528,46 @@ LocationHashbangInHtml5Url.prototype =
}
};
forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function (Location) {
Location.prototype = Object.create(locationPrototype);
/**
* @ngdoc method
* @name $location#state
*
* @description
* This method is getter / setter.
*
* Return the history state object when called without any parameter.
*
* Change the history state object when called with one parameter and return `$location`.
* The state object is later passed to `pushState` or `replaceState`.
*
* NOTE: This method is supported only in HTML5 mode and only in browsers supporting
* the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
* older browsers (like IE9 or Android < 4.0), don't use this method.
*
* @param {object=} state State object for pushState or replaceState
* @return {object} state
*/
Location.prototype.state = function(state) {
if (!arguments.length)
return this.$$state;
if (Location !== LocationHtml5Url || !this.$$html5) {
throw $locationMinErr('nostate', 'History API state support is available only ' +
'in HTML5 mode and only in browsers supporting HTML5 History API');
}
// The user might modify `stateObject` after invoking `$location.state(stateObject)`
// but we're changing the $$state reference to $browser.state() during the $digest
// so the modification window is narrow.
this.$$state = isUndefined(state) ? null : state;
return this;
};
});
function locationGetter(property) {
return function() {
return this[property];
@@ -584,7 +622,11 @@ function locationGetterSetter(property, preprocess) {
*/
function $LocationProvider(){
var hashPrefix = '',
html5Mode = false;
html5Mode = {
enabled: false,
requireBase: true,
rewriteLinks: true
};
/**
* @ngdoc method
@@ -606,12 +648,39 @@ function $LocationProvider(){
* @ngdoc method
* @name $locationProvider#html5Mode
* @description
* @param {boolean=} mode Use HTML5 strategy if available.
* @returns {*} current value if used as getter or itself (chaining) if used as setter
* @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
* If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported
* properties:
* - **enabled** `{boolean}` (default: false) If true, will rely on `history.pushState` to
* change urls where supported. Will fall back to hash-prefixed paths in browsers that do not
* support `pushState`.
* - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies
* whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are
* true, and a base tag is not present, an error will be thrown when `$location` is injected.
* See the {@link guide/$location $location guide for more information}
* - **rewriteLinks** - `{boolean}` - (default: `false`) When html5Mode is enabled, disables
* url rewriting for relative linksTurns off url rewriting for relative links.
*
* @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
*/
this.html5Mode = function(mode) {
if (isDefined(mode)) {
html5Mode = mode;
if (isBoolean(mode)) {
html5Mode.enabled = mode;
return this;
} else if (isObject(mode)) {
if (isBoolean(mode.enabled)) {
html5Mode.enabled = mode.enabled;
}
if (isBoolean(mode.requireBase)) {
html5Mode.requireBase = mode.requireBase;
}
if (isBoolean(mode.rewriteLinks)) {
html5Mode.rewriteLinks = mode.rewriteLinks;
}
return this;
} else {
return html5Mode;
@@ -623,14 +692,21 @@ function $LocationProvider(){
* @name $location#$locationChangeStart
* @eventType broadcast on root scope
* @description
* Broadcasted before a URL will change. This change can be prevented by calling
* Broadcasted before a URL will change.
*
* This change can be prevented by calling
* `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
* details about event object. Upon successful change
* {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
*
* The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
* the browser supports the HTML5 History API.
*
* @param {Object} angularEvent Synthetic event object.
* @param {string} newUrl New URL
* @param {string=} oldUrl URL that was before it was changed.
* @param {string=} newState New history state object
* @param {string=} oldState History state object that was before it was changed.
*/
/**
@@ -640,9 +716,14 @@ function $LocationProvider(){
* @description
* Broadcasted after a URL was changed.
*
* The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
* the browser supports the HTML5 History API.
*
* @param {Object} angularEvent Synthetic event object.
* @param {string} newUrl New URL
* @param {string=} oldUrl URL that was before it was changed.
* @param {string=} newState New history state object
* @param {string=} oldState History state object that was before it was changed.
*/
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
@@ -653,8 +734,8 @@ function $LocationProvider(){
initialUrl = $browser.url(),
appBase;
if (html5Mode) {
if (!baseHref) {
if (html5Mode.enabled) {
if (!baseHref && html5Mode.requireBase) {
throw $locationMinErr('nobase',
"$location in HTML5 mode requires a <base> tag to be present!");
}
@@ -667,13 +748,34 @@ function $LocationProvider(){
$location = new LocationMode(appBase, '#' + hashPrefix);
$location.$$parseLinkUrl(initialUrl, initialUrl);
$location.$$state = $browser.state();
var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
function setBrowserUrlWithFallback(url, replace, state) {
var oldUrl = $location.url();
var oldState = $location.$$state;
try {
$browser.url(url, replace, state);
// Make sure $location.state() returns referentially identical (not just deeply equal)
// state object; this makes possible quick checking if the state changed in the digest
// loop. Checking deep equality would be too expensive.
$location.$$state = $browser.state();
} catch (e) {
// Restore old values if pushState fails
$location.url(oldUrl);
$location.$$state = oldState;
throw e;
}
}
$rootElement.on('click', function(event) {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
if (event.ctrlKey || event.metaKey || event.which == 2) return;
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
var elm = jqLite(event.target);
@@ -699,6 +801,9 @@ function $LocationProvider(){
if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
if ($location.$$parseLinkUrl(absHref, relHref)) {
// We do a preventDefault for all urls that are part of the angular application,
// in html5mode and also without, so that we are able to abort navigation without
// getting double entries in the location history.
event.preventDefault();
// update location manually
if ($location.absUrl() != $browser.url()) {
@@ -716,52 +821,63 @@ function $LocationProvider(){
$browser.url($location.absUrl(), true);
}
// update $location when $browser url changes
$browser.onUrlChange(function(newUrl) {
if ($location.absUrl() != newUrl) {
$rootScope.$evalAsync(function() {
var oldUrl = $location.absUrl();
var initializing = true;
$location.$$parse(newUrl);
if ($rootScope.$broadcast('$locationChangeStart', newUrl,
oldUrl).defaultPrevented) {
$location.$$parse(oldUrl);
$browser.url(oldUrl);
} else {
afterLocationChange(oldUrl);
}
});
if (!$rootScope.$$phase) $rootScope.$digest();
}
// update $location when $browser url changes
$browser.onUrlChange(function(newUrl, newState) {
$rootScope.$evalAsync(function() {
var oldUrl = $location.absUrl();
var oldState = $location.$$state;
$location.$$parse(newUrl);
$location.$$state = newState;
if ($rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl,
newState, oldState).defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
setBrowserUrlWithFallback(oldUrl, false, oldState);
} else {
initializing = false;
afterLocationChange(oldUrl, oldState);
}
});
if (!$rootScope.$$phase) $rootScope.$digest();
});
// update browser
var changeCounter = 0;
$rootScope.$watch(function $locationWatch() {
var oldUrl = $browser.url();
var oldState = $browser.state();
var currentReplace = $location.$$replace;
if (!changeCounter || oldUrl != $location.absUrl()) {
changeCounter++;
if (initializing || oldUrl !== $location.absUrl() ||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state)) {
initializing = false;
$rootScope.$evalAsync(function() {
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
defaultPrevented) {
if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl,
$location.$$state, oldState).defaultPrevented) {
$location.$$parse(oldUrl);
$location.$$state = oldState;
} else {
$browser.url($location.absUrl(), currentReplace);
afterLocationChange(oldUrl);
setBrowserUrlWithFallback($location.absUrl(), currentReplace,
oldState === $location.$$state ? null : $location.$$state);
afterLocationChange(oldUrl, oldState);
}
});
}
$location.$$replace = false;
return changeCounter;
// we don't need to return anything because $evalAsync will make the digest loop dirty when
// there is a change
});
return $location;
function afterLocationChange(oldUrl) {
$rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
function afterLocationChange(oldUrl, oldState) {
$rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl,
$location.$$state, oldState);
}
}];
}
+5
View File
@@ -92,6 +92,11 @@ forEach({
CONSTANTS[name] = constantGetter;
});
//Not quite a constant, but can be lex/parsed the same
CONSTANTS['this'] = function(self) { return self; };
CONSTANTS['this'].sharedGetter = true;
//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter
var OPERATORS = extend(createMap(), {
/* jshint bitwise : false */
+45 -34
View File
@@ -128,14 +128,11 @@ function $RootScopeProvider(){
this.$$phase = this.$parent = this.$$watchers =
this.$$nextSibling = this.$$prevSibling =
this.$$childHead = this.$$childTail = null;
this['this'] = this.$root = this;
this.$root = this;
this.$$destroyed = false;
this.$$asyncQueue = [];
this.$$postDigestQueue = [];
this.$$listeners = {};
this.$$listenerCount = {};
this.$$isolateBindings = null;
this.$$applyAsyncQueue = [];
}
/**
@@ -184,18 +181,23 @@ function $RootScopeProvider(){
* When creating widgets, it is useful for the widget to not accidentally read parent
* state.
*
* @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent`
* of the newly created scope. Defaults to `this` scope if not provided.
* This is used when creating a transclude scope to correctly place it
* in the scope hierarchy while maintaining the correct prototypical
* inheritance.
*
* @returns {Object} The newly created child scope.
*
*/
$new: function(isolate) {
$new: function(isolate, parent) {
var child;
parent = parent || this;
if (isolate) {
child = new Scope();
child.$root = this.$root;
// ensure that there is just one async queue per $rootScope and its children
child.$$asyncQueue = this.$$asyncQueue;
child.$$postDigestQueue = this.$$postDigestQueue;
} else {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
@@ -212,16 +214,27 @@ function $RootScopeProvider(){
}
child = new this.$$ChildScope();
}
child['this'] = child;
child.$parent = this;
child.$$prevSibling = this.$$childTail;
if (this.$$childHead) {
this.$$childTail.$$nextSibling = child;
this.$$childTail = child;
child.$parent = parent;
child.$$prevSibling = parent.$$childTail;
if (parent.$$childHead) {
parent.$$childTail.$$nextSibling = child;
parent.$$childTail = child;
} else {
this.$$childHead = this.$$childTail = child;
parent.$$childHead = parent.$$childTail = child;
}
// When the new scope is not isolated or we inherit from `this`, and
// the parent scope is destroyed, the property `$$destroyed` is inherited
// prototypically. In all other cases, this property needs to be set
// when the parent scope is destroyed.
// The listener needs to be added after the parent is set
if (isolate || parent != this) child.$on('$destroy', destroyChild);
return child;
function destroyChild() {
child.$$destroyed = true;
}
},
/**
@@ -697,8 +710,6 @@ function $RootScopeProvider(){
$digest: function() {
var watch, value, last,
watchers,
asyncQueue = this.$$asyncQueue,
postDigestQueue = this.$$postDigestQueue,
length,
dirty, ttl = TTL,
next, current, target = this,
@@ -863,6 +874,10 @@ function $RootScopeProvider(){
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
// Disable listeners, watchers and apply/digest methods
this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop;
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
this.$$listeners = {};
// All of the code below is bogus code that works around V8's memory leak via optimized code
// and inline caches.
@@ -873,15 +888,7 @@ function $RootScopeProvider(){
// - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
this.$$childTail = this.$root = null;
// don't reset these to null in case some async task tries to register a listener/watch/task
this.$$listeners = {};
this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = [];
// prevent NPEs since these methods have references to properties we nulled out
this.$destroy = this.$digest = this.$apply = noop;
this.$on = this.$watch = this.$watchGroup = function() { return noop; };
this.$$childTail = this.$root = this.$$watchers = null;
},
/**
@@ -948,19 +955,19 @@ function $RootScopeProvider(){
$evalAsync: function(expr) {
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
if (!$rootScope.$$phase && !asyncQueue.length) {
$browser.defer(function() {
if ($rootScope.$$asyncQueue.length) {
if (asyncQueue.length) {
$rootScope.$digest();
}
});
}
this.$$asyncQueue.push({scope: this, expression: expr});
asyncQueue.push({scope: this, expression: expr});
},
$$postDigest : function(fn) {
this.$$postDigestQueue.push(fn);
postDigestQueue.push(fn);
},
/**
@@ -1044,7 +1051,7 @@ function $RootScopeProvider(){
*/
$applyAsync: function(expr) {
var scope = this;
expr && $rootScope.$$applyAsyncQueue.push($applyAsyncExpression);
expr && applyAsyncQueue.push($applyAsyncExpression);
scheduleApplyAsync();
function $applyAsyncExpression() {
@@ -1253,6 +1260,11 @@ function $RootScopeProvider(){
var $rootScope = new Scope();
//The internal queues. Expose them on the $rootScope for debugging/testing purposes.
var asyncQueue = $rootScope.$$asyncQueue = [];
var postDigestQueue = $rootScope.$$postDigestQueue = [];
var applyAsyncQueue = $rootScope.$$applyAsyncQueue = [];
return $rootScope;
@@ -1286,10 +1298,9 @@ function $RootScopeProvider(){
function initWatchVal() {}
function flushApplyAsync() {
var queue = $rootScope.$$applyAsyncQueue;
while (queue.length) {
while (applyAsyncQueue.length) {
try {
queue.shift()();
applyAsyncQueue.shift()();
} catch(e) {
$exceptionHandler(e);
}
+3 -6
View File
@@ -61,12 +61,9 @@ function $$SanitizeUriProvider() {
return function sanitizeUri(uri, isImage) {
var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
var normalizedVal;
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
if (!msie || msie >= 8 ) {
normalizedVal = urlResolve(uri).href;
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
return 'unsafe:'+normalizedVal;
}
normalizedVal = urlResolve(uri).href;
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
return 'unsafe:'+normalizedVal;
}
return uri;
};
-4
View File
@@ -8,7 +8,6 @@
* @requires $document
*
* @property {boolean} history Does the browser support html5 history api ?
* @property {boolean} hashchange Does the browser support hashchange event ?
* @property {boolean} transitions Does the browser support CSS transition events ?
* @property {boolean} animations Does the browser support CSS animation events ?
*
@@ -65,9 +64,6 @@ function $SnifferProvider() {
// jshint -W018
history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
// jshint +W018
hashchange: 'onhashchange' in $window &&
// IE8 compatible mode lies
(!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
+110 -47
View File
@@ -221,7 +221,7 @@
*
* ### CSS Staggering Animations
* A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
* curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
* curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
* performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
* the animation. The style property expected within the stagger class can either be a **transition-delay** or an
* **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
@@ -377,6 +377,7 @@ angular.module('ngAnimate', ['ng'])
var forEach = angular.forEach;
var selectors = $animateProvider.$$selectors;
var isArray = angular.isArray;
var isString = angular.isString;
var ELEMENT_NODE = 1;
var NG_ANIMATE_STATE = '$$ngAnimateState';
@@ -467,34 +468,32 @@ angular.module('ngAnimate', ['ng'])
return defer.promise;
}
function parseAnimateOptions(options) {
// some plugin code may still be passing in the callback
// function as the last param for the $animate methods so
// it's best to only allow string or array values for now
if (isArray(options)) return options;
if (isString(options)) return [options];
}
function resolveElementClasses(element, cache, runningAnimations) {
runningAnimations = runningAnimations || {};
var map = {};
forEach(cache.add, function(className) {
if (className && className.length) {
map[className] = map[className] || 0;
map[className]++;
}
});
forEach(cache.remove, function(className) {
if (className && className.length) {
map[className] = map[className] || 0;
map[className]--;
}
});
var lookup = [];
var lookup = {};
forEach(runningAnimations, function(data, selector) {
forEach(selector.split(' '), function(s) {
lookup[s]=data;
});
});
var hasClasses = Object.create(null);
forEach((element.attr('class') || '').split(/\s+/), function(className) {
hasClasses[className] = true;
});
var toAdd = [], toRemove = [];
forEach(map, function(status, className) {
var hasClass = angular.$$hasClass(element[0], className);
forEach(cache.classes, function(status, className) {
var hasClass = hasClasses[className];
var matchingAnimation = lookup[className] || {};
// When addClass and removeClass is called then $animate will check to
@@ -505,12 +504,12 @@ angular.module('ngAnimate', ['ng'])
// Once an animation is allowed then the code will also check to see if
// there exists any on-going animation that is already adding or remvoing
// the matching CSS class.
if (status < 0) {
if (status === false) {
//does it have the class or will it have the class
if (hasClass || matchingAnimation.event == 'addClass') {
toRemove.push(className);
}
} else if (status > 0) {
} else if (status === true) {
//is the class missing or will it be removed?
if (!hasClass || matchingAnimation.event == 'removeClass') {
toAdd.push(className);
@@ -794,7 +793,8 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
* @return {Promise} the animation callback promise
*/
enter : function(element, parentElement, afterElement) {
enter : function(element, parentElement, afterElement, options) {
options = parseAnimateOptions(options);
element = angular.element(element);
parentElement = prepareElement(parentElement);
afterElement = prepareElement(afterElement);
@@ -802,7 +802,7 @@ angular.module('ngAnimate', ['ng'])
classBasedAnimationsBlocked(element, true);
$delegate.enter(element, parentElement, afterElement);
return runAnimationPostDigest(function(done) {
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
return performAnimation('enter', 'ng-enter', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
});
},
@@ -836,16 +836,16 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} element the element that will be the focus of the leave animation
* @return {Promise} the animation callback promise
*/
leave : function(element) {
leave : function(element, options) {
options = parseAnimateOptions(options);
element = angular.element(element);
cancelChildAnimations(element);
classBasedAnimationsBlocked(element, true);
this.enabled(false, element);
return runAnimationPostDigest(function(done) {
return performAnimation('leave', 'ng-leave', stripCommentsFromElement(element), null, null, function() {
$delegate.leave(element);
}, done);
}, options, done);
});
},
@@ -882,7 +882,8 @@ angular.module('ngAnimate', ['ng'])
* @param {DOMElement} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
* @return {Promise} the animation callback promise
*/
move : function(element, parentElement, afterElement) {
move : function(element, parentElement, afterElement, options) {
options = parseAnimateOptions(options);
element = angular.element(element);
parentElement = prepareElement(parentElement);
afterElement = prepareElement(afterElement);
@@ -891,7 +892,7 @@ angular.module('ngAnimate', ['ng'])
classBasedAnimationsBlocked(element, true);
$delegate.move(element, parentElement, afterElement);
return runAnimationPostDigest(function(done) {
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, done);
return performAnimation('move', 'ng-move', stripCommentsFromElement(element), parentElement, afterElement, noop, options, done);
});
},
@@ -924,8 +925,8 @@ angular.module('ngAnimate', ['ng'])
* @param {string} className the CSS class that will be added to the element and then animated
* @return {Promise} the animation callback promise
*/
addClass : function(element, className) {
return this.setClass(element, className, []);
addClass : function(element, className, options) {
return this.setClass(element, className, [], options);
},
/**
@@ -957,8 +958,8 @@ angular.module('ngAnimate', ['ng'])
* @param {string} className the CSS class that will be animated and then removed from the element
* @return {Promise} the animation callback promise
*/
removeClass : function(element, className) {
return this.setClass(element, [], className);
removeClass : function(element, className, options) {
return this.setClass(element, [], className, options);
},
/**
@@ -988,33 +989,68 @@ angular.module('ngAnimate', ['ng'])
* CSS classes have been set on the element
* @return {Promise} the animation callback promise
*/
setClass : function(element, add, remove) {
setClass : function(element, add, remove, options) {
options = parseAnimateOptions(options);
var STORAGE_KEY = '$$animateClasses';
element = angular.element(element);
element = stripCommentsFromElement(element);
if (classBasedAnimationsBlocked(element)) {
return $delegate.setClass(element, add, remove);
// TODO(@caitp/@matsko): Don't use private/undocumented API here --- we should not be
// changing the DOM synchronously in this case. The `true` parameter must eventually be
// removed.
return $delegate.setClass(element, add, remove, true);
}
add = isArray(add) ? add : add.split(' ');
remove = isArray(remove) ? remove : remove.split(' ');
// we're using a combined array for both the add and remove
// operations since the ORDER OF addClass and removeClass matters
var classes, cache = element.data(STORAGE_KEY);
var hasCache = !!cache;
if (!cache) {
cache = {};
cache.classes = {};
}
classes = cache.classes;
var cache = element.data(STORAGE_KEY);
if (cache) {
cache.add = cache.add.concat(add);
cache.remove = cache.remove.concat(remove);
add = isArray(add) ? add : add.split(' ');
forEach(add, function(c) {
if (c && c.length) {
classes[c] = true;
}
});
remove = isArray(remove) ? remove : remove.split(' ');
forEach(remove, function(c) {
if (c && c.length) {
classes[c] = false;
}
});
if (hasCache) {
if (options && cache.options) {
cache.options = cache.options.concat(options);
}
//the digest cycle will combine all the animations into one function
return cache.promise;
} else {
element.data(STORAGE_KEY, cache = {
add : add,
remove : remove
classes : classes,
options : options
});
}
return cache.promise = runAnimationPostDigest(function(done) {
var parentElement = element.parent();
var elementNode = extractElementNode(element);
var parentNode = elementNode.parentNode;
// TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed
if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) {
done();
return;
}
var cache = element.data(STORAGE_KEY);
element.removeData(STORAGE_KEY);
@@ -1022,9 +1058,10 @@ angular.module('ngAnimate', ['ng'])
var classes = resolveElementClasses(element, cache, state.active);
return !classes
? done()
: performAnimation('setClass', classes, element, null, null, function() {
$delegate.setClass(element, classes[0], classes[1]);
}, done);
: performAnimation('setClass', classes, element, parentElement, null, function() {
if (classes[0]) $delegate.$$addClassImmediately(element, classes[0]);
if (classes[1]) $delegate.$$removeClassImmediately(element, classes[1]);
}, cache.options, done);
});
},
@@ -1086,7 +1123,7 @@ angular.module('ngAnimate', ['ng'])
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
var noopCancel = noop;
var runner = animationRunner(element, animationEvent, className);
@@ -1194,6 +1231,11 @@ angular.module('ngAnimate', ['ng'])
//the ng-animate class does nothing, but it's here to allow for
//parent animations to find and cancel child animations when needed
element.addClass(NG_ANIMATE_CLASS_NAME);
if (isArray(options)) {
forEach(options, function(className) {
element.addClass(className);
});
}
var localAnimationCount = globalAnimationCounter++;
totalActiveAnimations++;
@@ -1263,8 +1305,15 @@ angular.module('ngAnimate', ['ng'])
function closeAnimation() {
if (!closeAnimation.hasBeenRun) {
closeAnimation.hasBeenRun = true;
if (isArray(options)) {
forEach(options, function(className) {
element.removeClass(className);
});
}
var data = element.data(NG_ANIMATE_STATE);
if (data) {
/* only structural animations wait for reflow before removing an
animation, but class-based animations don't. An example of this
failing would be when a parent HTML tag has a ng-class attribute
@@ -1419,6 +1468,16 @@ angular.module('ngAnimate', ['ng'])
var parentCounter = 0;
var animationReflowQueue = [];
var cancelAnimationReflow;
function clearCacheAfterReflow() {
if (!cancelAnimationReflow) {
cancelAnimationReflow = $$animateReflow(function() {
animationReflowQueue = [];
cancelAnimationReflow = null;
lookupCache = {};
});
}
}
function afterReflow(element, callback) {
if (cancelAnimationReflow) {
cancelAnimationReflow();
@@ -1519,7 +1578,7 @@ angular.module('ngAnimate', ['ng'])
function parseMaxTime(str) {
var maxValue = 0;
var values = angular.isString(str) ?
var values = isString(str) ?
str.split(/\s*,\s*/) :
[];
forEach(values, function(value) {
@@ -1764,6 +1823,7 @@ angular.module('ngAnimate', ['ng'])
//to perform at all
var preReflowCancellation = animateBefore(animationEvent, element, className);
if (!preReflowCancellation) {
clearCacheAfterReflow();
animationComplete();
return;
}
@@ -1820,6 +1880,7 @@ angular.module('ngAnimate', ['ng'])
afterReflow(element, animationCompleted);
return cancellationMethod;
}
clearCacheAfterReflow();
animationCompleted();
},
@@ -1829,6 +1890,7 @@ angular.module('ngAnimate', ['ng'])
afterReflow(element, animationCompleted);
return cancellationMethod;
}
clearCacheAfterReflow();
animationCompleted();
},
@@ -1838,6 +1900,7 @@ angular.module('ngAnimate', ['ng'])
afterReflow(element, animationCompleted);
return cancellationMethod;
}
clearCacheAfterReflow();
animationCompleted();
},

Some files were not shown because too many files have changed in this diff Show More