Compare commits

..

133 Commits

Author SHA1 Message Date
Jeff Cross 3468ad1b61 chore(release): cut 1.2.5 singularity-expansion release 2013-12-13 10:52:13 -08:00
Jeff Cross e9c79cad43 docs(CHANGELOG): add v1.2.5 changes 2013-12-13 10:44:40 -08:00
Igor Minar e455e7d878 docs(TRIAGING): add info about "needs: public api" 2013-12-13 08:53:33 -08:00
Michał Gołębiowski 3410f65e79 perf(jqLite): implement and use the empty method in place of html(‘’)
jQuery's elem.html('') is way slower than elem.empty(). As clearing
element contents happens quite often in certain scenarios, switching
to using .empty() provides a significant performance boost when using
Angular with jQuery.

Closes #4457
2013-12-13 02:07:11 -08:00
Karl Seamon f3de5b6eac perf(a): do not link when href or name exists in template
Change the a directive to link and hookup a click event only when
there is no href or name in the template element.
In a large Google app, this results in about 800 fewer registrations,
saving a small but measurable amount of time and memory.

Closes #5362
2013-12-13 00:31:25 -08:00
Karl Seamon fcd2a8131a perf($resource): use shallow copy instead of angular.copy
Replace calls to angular.copy with calls to a new function, shallowClearAndCopy.
Add calls to copy for cache access in $http in order to prevent modification of cached data.
Results in a measurable improvement to the startup time of complex apps within Google.

Closes #5300
2013-12-13 00:24:37 -08:00
Karl Seamon 62dbe85798 perf: use call and === instead of apply and == in type check functions
Updates isDate et al to use call instead of apply and === instead of ==.
The change to call brings minor performance improvement and === is just
better practice than ==.
http://jsperf.com/call-vs-apply-tostring

Closes #5295
2013-12-13 00:22:47 -08:00
Brian Atkinson 1d5e18b062 fix(closure): add missing FormController extern definitions
Closes #5303
2013-12-13 00:14:31 -08:00
Igor Minar a0ed371389 style($injector): remove ws 2013-12-12 23:51:45 -08:00
Ben Wiklund 05e4fd3488 perf($injector): remove invoke optimization that doesn't work
Closes #5388
2013-12-12 23:49:03 -08:00
Tobias Bosch 30a8b7d0b5 fix(ngInclude): Add template to DOM before linking other directives
The template needs to be added to the DOM before
other directives at the same element as `ngInclude` are linked.

Fixes #5247.
2013-12-12 17:18:44 -08:00
Tobias Bosch f8944efe70 fix(ngView): Add template to DOM before linking other directives
The template needs to be added to the DOM before
other directives at the same element as `ngView` are linked.

Related to #5247.
2013-12-12 17:18:44 -08:00
Tobias Bosch 43072e3812 fix($compile): Allow literals in isolate scope references
When a component uses an isolate scope reference
and the the component is used with an object literal
a new object is created on every evaluation.
Therefore the compiler needs to compare
the values of the parent and the isolate scope
using object equality and not object reference
equality.

Fixes #5296.
2013-12-12 16:30:31 -08:00
Pete Bacon Darwin 9396d55414 docs(ngSwitch): clarify that ngSwitch matches against string literals
Closes #5285
2013-12-12 11:42:49 +00:00
mbrookes 82e97cf53e docs(guide/expression): remove misplaced comma
Closes #5280
2013-12-12 11:25:41 +00:00
Jürgen Walter cf2a7614a4 docs(tutorial/step-07): update path to pages in e2e scenarios
The url paths in the tutorial are not in line with the actual tutorial code

Closes #5264
2013-12-12 11:22:31 +00:00
Vlad GURDIGA 9e538e7c31 docs(ng.$rootScope.Scope): fix API links
Also added a note to the Writing AngularJS Documentation:
https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation/d0c715ef89

Closes #5261
2013-12-12 11:19:16 +00:00
Mattias Holmlund 4ac21ac039 docs(error/transclude/orphan): fix spelling mistakes
Closes #5259
2013-12-12 11:15:31 +00:00
Karl Seamon f69dc16241 fix(angular-mocks): use copy of mock data in $httpBackend
Copy mock data returned from the mock $httpBackend.
This prevents modifications to the response from affecting future responses.
Previously, this misbehavior was being mitigated by the deep copy in $resource, but that no longer exists.
2013-12-11 15:18:45 -08:00
Brian Ford f1a8d419d5 chore(scripts): fix bower script to pull from master before updating 2013-12-11 14:35:23 -08:00
Vojta Jina 8864e54f1f chore(scripts): refactor travis scripts
Refactoring so that it's easier to use both SL/BS just depending on a global switch.
2013-12-11 10:12:18 -08:00
Vojta Jina dc4df93177 chore(travis): run two jobs
Instead of parallelization on a single Travis VM, we use two VMs.
- output is nicer (we don't have to buffer e2e tests and then show it at the end)
- you can easily see faster the result of unit tests (as it's basically a separate build)

We should also make sure we only do the necesary stuff (for install we don't need to do `grunt
package` for unit tests, we only need to generate the docs for e2e tests.
2013-12-11 10:02:14 -08:00
Tobias Bosch 043190f397 docs(TRIAGING): Add Type:Perf label 2013-12-09 17:54:08 -08:00
Tobias Bosch f4d850e168 docs(TRIAGING): Labels cla yes/no are automatically set
The labels are set by a script and
should not be set manually any more.
2013-12-09 17:30:43 -08:00
Andy Ferra 8ec2743ca1 docs(design): guide index formatting
Just my first pass at a more readable format of the guide index.

Note: the styles apply to all content in the docs, not just the guide
index. This is intentional and I feel that the result is positive.
2013-12-09 16:32:10 -08:00
Vojta Jina ecbee8147b style($route): make jshint happy
Fix the broken build and earn a late (french spelling).
2013-12-09 14:57:49 -08:00
miknsh5 f8c6ee3df5 docs(input): remove redundant closing span tag
Closes #5257
2013-12-09 20:52:00 +00:00
hambyiii fe84f7bef8 docs($route): clarify examples of route parameters
Putting route parameter examples in braces was misleading newcomers.

Closes #5243
2013-12-09 20:48:50 +00:00
Robin Böhm d653607162 docs($q): correct typo in the 'Differences Q/$q' section
Closes #5230
2013-12-09 20:46:21 +00:00
gdi2290 082fe180ec docs(contribute) add platform agnostic git install link
Closes #5216
2013-12-09 20:27:08 +00:00
Shane M. Pelletier d3491083a5 docs(tutorial): enlarge clickable area of tutorial nav buttons
Change position of <a> and <li> tags in tutorial nav buttons
partial. This allows the full area of the button to be clicked
rather than just the text.

Closes #5074
Closes #5209
2013-12-09 20:24:54 +00:00
Wesley Cho c3d6ca97e1 docs(guide/forms): clarify how NgModelController can be added to scope
Closes #5200
2013-12-09 20:14:45 +00:00
Matias Niemelä a14266e464 chore(CHANGELOG): introduce perf() section for performance-related commits
Instead of using fix() or chore() when labelling a commit which improves
speed or performance use perf(). Perf commits will be listed in the
CHANGELOG under "Performance Improvements".

For example:
perf($animate): cache all getComputedStyle operations to reduce additional reflows
2013-12-09 11:52:28 -05:00
Joscha Feth b4d44e1298 docs($injector): add example on how to use the element.injector
Closes #5188
2013-12-09 16:32:09 +00:00
Matias Niemelä ca116c35a6 chore(release): start 1.2.5 singularity-expansion iteration 2013-12-06 14:51:45 -05:00
Matias Niemelä 78ba429e6a chore(CHANGELOG): remove reverted commits from 1.2.4 2013-12-06 13:14:56 -05:00
Matias Niemelä dbf8c3c745 revert: chore(Angular.js): Use call and === instead of apply and == in type check functions 2013-12-06 13:14:08 -05:00
Matias Niemelä 3602c9785b revert: chore($resource): Use shallow copy instead of angular.copy 2013-12-06 13:13:48 -05:00
Matias Niemelä acaac21fd1 chore(release): cut 1.2.4 wormhole-blaster release 2013-12-06 12:46:25 -05:00
Matias Niemelä c98ef94706 docs(CHANGELOG): add v1.2.4 changes 2013-12-06 12:46:01 -05:00
Tobias Bosch b0972a2e75 fix($compile): update cloned elements if the template arrives after the cloning
If an element has a directive whose content is loaded using `templateUrl`,
and the element is cloned using a linking function before the template arrives,
the clone needs to be updated as well.

This also updates `ngIf` and `ngRepeat` to keep the connection to the clone
of a tranclude function, so that they know about the changes a directive with
`templateUrl` does to the element in the future.

Fixes to #4930.
2013-12-05 22:16:25 -08:00
Caitlin Potter 2dbb6f9a54 fix(isElement): return boolean value rather than truthy value.
angular.isElement currently returns a truthy object/function, or false. This
patch aims to correct this behaviour by casting the result of the isElement
expression to a boolean value via double-negation.

Closes #4519
Closes #4534
2013-12-05 17:14:05 -08:00
Karl Seamon 785a5fd7c1 chore(Angular.js): Use call and === instead of apply and == in type check functions
Updates isDate et al to use call instead of apply and === instead of ==.
The change to call brings minor performance improvement and === is just
better practice than ==.
http://jsperf.com/call-vs-apply-tostring

Closes #5295
2013-12-05 16:43:11 -08:00
Karl Seamon a55c1e79cf chore($resource): Use shallow copy instead of angular.copy
Replace calls to angular.copy with calls to a new function, shallowClearAndCopy.
Add calls to copy for cache access in $http in order to prevent modification of cached data.
Results in a measurable improvement to the startup time of complex apps within Google.

Closes #5300
2013-12-05 16:13:04 -08:00
Karl Seamon d070450cd2 chore(Scope): short-circuit after dirty-checking last dirty watcher
Stop dirty-checking during $digest after the last dirty watcher has been re-checked.

This prevents unneeded re-checking of the remaining watchers (They were already
checked in the previous iteration), bringing a substantial performance improvement
to the average case run time of $digest.

Closes #5272
Closes #5287
2013-12-05 15:37:37 -08:00
Andres Kalle 09648e4888 docs(tutorial/step-6): remove unused class="diagram"
Closes #5197
2013-12-05 22:17:07 +00:00
Pete Bacon Darwin 2adbcf189b docs(tutorial/step-3): remember to install karma plugins 2013-12-05 22:06:38 +00:00
Pete Bacon Darwin 39c5ffb2a6 docs(tutorial/step-2): remember to install karma plugins 2013-12-05 22:06:28 +00:00
Tobias Bosch 04a570d31c docs(TRIAGING): Initial doc about triaging issues in Angular 2013-12-05 09:36:14 -08:00
Matias Niemelä 958d3d56b1 fix($animate): ensure animations work with directives that share a transclusion
Closes #4716
Closes #4871
Closes #5021
Closes #5278
2013-12-05 10:54:19 -05:00
Caitlin Potter 0e50810c53 fix(ngInit): evaluate ngInit before ngInclude
The priority of ngInit is adjusted to occur before ngInclude, and after
ngController. This enables ngInit to initiallize values in a controller's
scope, and also to initiallize values before ngInclude executes.

Closes #5167
Closes #5208
2013-12-04 23:26:56 -08:00
Vojta Jina 21e48abbc1 chore(travis): move checks from before_scripts to scripts
If jshint (or any other ci-check) fails, Travis marks the build as "Errored" which I don't think is desider:
https://travis-ci.org/angular/angular.js/builds/14938896
2013-12-04 22:53:54 -08:00
Daniel Tabuenca b6d5439343 fix(input): ensure ngModelWatch() triggers second digest pass when appropriate
Due to an earlier change, ngModelWatch() no longer returns a value to the
caller. This means the digest loop has no way to tell if the watch actually
modified anything and so can not schedule another pass.

This means any watches that watch form or model controller changes
(e.g. watches on form.$valid) that are scheduled prior to an ngModelWatch()
will not be able to see any changes made therin.

This commit fixes this behavior by returning the latest evaluated ng-model
value.

Closes #5258
Closes #5282
2013-12-04 22:49:11 -08:00
Matias Niemelä 93901bdde4 fix($animate): ensure ms durations are properly rounded
Closes #5113
Closes #5162
2013-12-04 19:26:40 -05:00
Jeff Cross d802ed1b36 fix($rootScope): broadcast $destroy event on $rootScope
Fixes #5169
2013-12-04 15:29:19 -08:00
Sorin Gitlan e8f4305e9d docs($interpolate): demonstrate a filter in the interpolated expression
Closes #5186
2013-12-04 22:36:42 +00:00
Iwona Lalik b38a2287f2 docs(tutorial/step-3): add module to ng-app directive in code sample
Closes #5184
2013-12-04 22:36:42 +00:00
Julien Bouquillon 1e7675ad4c docs(input): remove deprecated isolated scope pitfall
The 1.2 release fixed the documented pitfall at 909cabd36d
by isolating only the isolated directive's scope.

Closes #5179
2013-12-04 22:36:41 +00:00
David Bennett 280b5ce3c0 chore(closure): add $routeProvider#redirectTo function parameters
Closes #5173
2013-12-04 22:36:41 +00:00
Elwin Arens fbc5cf514b docs(tutorial/step-12): fix refernce to incorrect jquery version
Closes #5156
2013-12-04 22:36:40 +00:00
Mateusz Jedruch f01087f802 fix(closure): closure compiler shouldn't rename .defaults.transformRequest 2013-12-04 14:01:34 -08:00
Yves Richard 4ac6424e87 docs(animate.js): fix copy/paste typo in leave docs
fix the copy/paste typo within $animate.leave method inside of the $animate docs.
2013-12-04 14:28:32 -05:00
Thomas Guillory d3c486dd6d fix($rootScope): clear phase if an exception is raised by a watcher
Add calls to clearPhase() when an exception is raised by a watcher
while a digest cycle, in order to not be stuck on `$digest` scope phase
2013-12-04 10:11:29 -08:00
Daniel Tabuenca 2d0f6ccba8 fix($compile): ensure isolated local watches' lastValue is always in sync
When using two-way binding with isolate scope, under some circumstances
the lastValue variable captured in the parentValueWatch function can get
out of sync.

Specifically, if both the value in the origin scope as well as the value
in the isolate scope get independently updated to the same value within
one digest cycle, the lastValue is never updated. This potentially causes
the watch to make the wrong decision as to which side to update on subsequent
passes.

This fixes things by ensuring lastValue is always set to the last seen
value even if the watch's logic was short circuited because there was no
difference between the values in the original and isolate scopes.

Closes #5182
2013-12-04 09:45:20 -08:00
Vojta Jina 9a81b8668a chore(travis): give browsers more time to respond 2013-12-03 19:32:32 -08:00
Vojta Jina 9481d69d1c chore(travis): add IE 11 (SL) to the build 2013-12-03 19:32:32 -08:00
Vojta Jina 7615723547 chore: make it simpler to run tests on SL/BS during local development 2013-12-03 16:07:13 -08:00
Vojta Jina 338f949259 chore(travis): set SauceLabs build id 2013-12-03 15:49:19 -08:00
Vojta Jina d0192b31a3 chore(travis): disable IE11
For some reason it's broken on SL.
2013-12-03 15:49:19 -08:00
Vojta Jina 6127528b50 chore(travis): switch back to SauceLabs
I think we are pretty close to be able to use both.

The xhr-polling seems to be pretty stable, but I'm having problems with multiple SSH tunnels (on BS), so let's try to switch back to SL.
2013-12-03 15:49:19 -08:00
Vojta Jina 0410572322 chore(travis): define a launcher for IE11 (SauceLabs) 2013-12-03 15:49:19 -08:00
Vojta Jina fd2371cfc2 chore(travis): report both build number and id to BS 2013-12-03 15:49:19 -08:00
Vojta Jina 267fcc999c chore(travis): log used ports 2013-12-03 15:49:18 -08:00
Vojta Jina 84187b6d94 chore(travis): use different port numbers per build
We can't establish multiple SSH tunnels for the same port (for BrowserStack).
This makes it possible to run multiple parallel builds using BrowserStack.
2013-12-03 15:49:18 -08:00
Vojta Jina 5d6482bb3b chore(karma): correct the 404 ignoring 2013-12-03 15:49:18 -08:00
Naomi Black 023765c593 docs: fixed a typo and made a minor edit to docs section of CONTRIBUTING.md 2013-12-03 15:44:56 -08:00
Igor Minar 4a401bbcf3 style(Scope): remove extra ws 2013-12-03 15:44:13 -08:00
Igor Minar 7401c70718 style(Scope): rename child scope type from Child to ChildScope
This change makes it easier to debug angular, especiall when dealing with heap snapshots
and hunting for memory leaks.
2013-12-03 15:44:13 -08:00
Igor Minar bb36bc7edf style(Angular.js): fix typo in comment 2013-12-03 15:44:13 -08:00
Michał Gołębiowski bf1972dc1e fix(ngSanitize): prefer textContent to innerText to avoid layout trashing
innerText depends on styling as it doesn't display hidden elements.
Therefore, it's better to use textContent not to cause unnecessary
reflows. However, IE<9 don't support textContent so the innerText
fallback is necessary.
2013-12-03 14:45:30 -08:00
Karl Seamon 689dfb1679 chore($parse): micro-optimization for ensureSafeObject function
This version matches the "alternate 2.2" version here: http://jsperf.com/ensuresafeobject/2

alternate 2.3 is a bit faster and simpler, but would break backwards compatibility.

Closes #5246
2013-12-03 11:10:48 -08:00
Hubert SABLONNIÈRE 1169b54456 fix(jqLite): ignore incompatible nodes on find()
When a jqLite collection contains text nodes, find() does not work :-(

This fix ignores all nodes than can't do getElementsByTagName()

It seems a little bit faster than testing nodeType : http://jsperf.com/nodetype-vs-duck-typing

Closes #4120
2013-12-03 09:45:06 -08:00
Pete Bacon Darwin 81b81856ee fix($sanitize): don't rely on YARR regex engine executing immediately
In Safari 7 (and other browsers potentially using the latest YARR JIT library)
regular expressions are not always executed immediately that they are called.
The regex is only evaluated (lazily) when you first access properties on the `matches`
result object returned from the regex call.

In the case of `decodeEntities()`, we were updating this returned object, `parts[0] = ''`,
before accessing it, `if (parts[2])', and so our change was overwritten by the result
of executing the regex.

The solution here is not to modify the match result object at all. We only need to make use
of the three match results directly in code.

Developers should be aware, in the future, when using regex, to read from the result object
before making modifications to it.

There is no additional test committed here, because when run against Safari 7, this
bug caused numerous specs to fail, which are all fixed by this commit.

Closes #5193
Closes #5192
2013-12-03 13:35:09 +00:00
Vojta Jina fd4b99936e chore(travis): increase BrowserStack timeout to 10min
Because IE is retarded.
2013-12-02 22:51:15 -08:00
Vojta Jina 09271a8ab9 chore(travis): ignore 404 warnings, debug log into file
This is a terrible hack/workaround, however I don't think there is any better way to achieve this
with log4js.
2013-12-02 22:51:15 -08:00
Naomi Black 5a8d9acacb docs: update CONTRIBUTING.MD with process for doc fixes 2013-12-02 16:23:59 -08:00
Vojta Jina 04d5a5072f chore(travis): fix the build id on browser stack
This just improves the way BrowserStack groups the sessions.
2013-12-02 14:43:44 -08:00
Vojta Jina 55c30e1be6 chore: use karma-browserstack-launcher from master 2013-12-02 14:20:14 -08:00
Vojta Jina 97fc84c151 chore(deps): use regular junit-reporter
We don't need the special branch anymore.
2013-12-02 14:20:06 -08:00
Vojta Jina 4ee0687f3f chore(travis): tolerate 2 disconnects to make the build more stable 2013-12-02 14:19:56 -08:00
Vojta Jina ddff347b91 chore(travis): use only websockets and xhr-polling
I wanna see more info about BrowserStack...
2013-12-02 14:19:46 -08:00
James Brewer 05ef1bd853 chore(grunt): update to latest jshint task
Upgrade JSHint task from ~0.6.4 to ~0.7.2. Two useful changes: ability
to set jshintrc option to use jshint's native ability for finding .jshintrc
files relative to the linted files and update jshint to 2.3.0.

Closes #5143
2013-12-02 21:20:07 +00:00
Blaise Kal d0f8bd30a6 docs($animate): require ngAnimate in example, syntax fixes
The example in the section "JavaScript-defined Animations" would not run without the ngAnimate dependency. Also added a missing comma and semicolons.
2013-12-02 14:55:34 -05:00
Peter Bacon Darwin 1a8d3c8b3a chore(docs): fix back-to-top anchor in angularjs.org doc pages
Closes https://github.com/angular/angularjs.org/issues/45
2013-11-28 12:16:07 +00:00
Levi Weiss 753687e5c2 docs(tutorial/step-10): fix typo
Closes #5171
2013-11-27 23:21:46 +00:00
Stéphane Reynaud 1a15c01b64 docs($compile): fix missing space 2013-11-27 23:20:09 +00:00
Ammar 7f33e1ca89 docs(tutorial/step-12): fix typo
Closes #5148
2013-11-27 23:19:02 +00:00
magoswiat 28d00945ba docs(tutorial/step-0): add target="_blank" to open app in new page
Closes #5145
2013-11-27 23:17:28 +00:00
Blaise Kal 6f40c88f47 docs(form): provide a list of Angular's built-in validation tokens
As requested by a top-rated Disqus comment: http://docs.angularjs.org/api/ng.directive:form.FormController#comment-655325797

Closes #5121
2013-11-27 23:06:19 +00:00
deepak-kapoor 68dd621082 docs(guide/concepts): fix incorrect module name in example
Closes #5116
2013-11-27 22:59:13 +00:00
wjtk 3abfb4ef51 docs($window): move use of $window to controller
Move use of `$window` from template to controller, because accessing `$window`
in expressions is now disallowed and doesn't work.

Closes #5110
2013-11-27 22:54:45 +00:00
Pete Bacon Darwin 1014e52349 docs($injector): use square bracket notation for $inject annotation
Closes #5104
2013-11-27 22:52:10 +00:00
Evan Winslow cda061f723 docs(guide/di): use square bracket notation for $inject annotation
Closes #5104
2013-11-27 22:48:02 +00:00
Pavel Pomerantsev 1497c6c1fb docs(guide/providers): fix typo
Closes #5102
2013-11-27 22:41:53 +00:00
Marc Lipovsky e41e445b51 docs(guide/compiler): add fourth step on appending the compiled template to the DOM
Closes #5087
2013-11-27 22:41:10 +00:00
rodyhaddad 7ab73190b7 docs(migration): add a note about "private" properties being reverted
Closes #5086
2013-11-27 22:35:09 +00:00
Peter Bacon Darwin 450b3a5460 chore(release): start 1.2.4 wormhole-baster iteration 2013-11-27 20:21:06 +00:00
Peter Bacon Darwin 38fb542838 chore(release): cut 1.2.3 unicorn-zapper release 2013-11-27 10:04:59 +00:00
Peter Bacon Darwin 7ab5098c14 docs(CHANGELOG): add v1.2.3 changes 2013-11-27 09:58:59 +00:00
Jeff Cross bcca80548d feat($attrs): add $attrs.$attr to externs so that it isn't renamed
This fixes the issue that any usage of $attr is broken after js compilation.
2013-11-26 18:34:11 -08:00
Jeff Cross 736c8fbbae refactor($location): move file://+win path fix to $location
The urlResolve method was fixed to automatically remove the
volume label from path names to fix issues with the file
protocol on windows where $location.path() was returning
paths where the first segment would be the volume name,
such as "/C:/mypath". See #4942 and #4928

However, the solution was specific to the $location non-
HTML5 mode, and was implemented at a lower level of
abstraction than it should have been. This refactor moves
the fix to inside of the LocationHashBangUrl $$parse method.

Closes #5041
2013-11-26 18:31:27 -08:00
Igor Minar 947562220d chore(release): fix cdn version in package.json 2013-11-26 17:38:23 -08:00
Tobias Bosch 333523483f fix($sanitize): Use same whitelist mechanism as $compile does.
`$sanitize` now uses the same mechanism as `$compile` to validate uris.
By this, the validation in `$sanitize` is more general and can be
configured in the same way as the one in `$compile`.

Changes
- Creates the new private service `$$sanitizeUri`.
- Moves related specs from `compileSpec.js` into `sanitizeUriSpec.js`.
- Refactors the `linky` filter to be less dependent on `$sanitize`
  internal functions.

Fixes #3748.
2013-11-26 14:29:38 -08:00
corrupt 68ceb17272 chore($httpBackend): preserve original non-zero http status code for file:// apps
Previously if an app was running from file:// origin we would always return either
http 200 or 404 depending on whether the response was present.

This changes the behavior so that we do this only if the protocol of the request
(not the origin) is file:// and only if the status code is 0.

Closes #4436
Closes #4587
Closes #4514
2013-11-26 12:36:41 -08:00
David Mosher 5bd6596856 chore(mocks): wrap angular-mocks.js in closure
Closes #5080
2013-11-26 13:22:29 +00:00
Peter Bacon Darwin b3f2a20832 chore(changelog): remove tmp file 2013-11-26 09:36:13 +00:00
adam77 e8d8c7a8d7 docs(compile): fix typo
Closes #5133
2013-11-26 06:56:38 +00:00
Deepak Kapoor 7a91d7fa7e docs(CONTRIBUTING): fix broken link to GitHub PR Helper
Closes #5134
2013-11-26 06:46:39 +00:00
smarigowda c6bd58eb58 docs(guide/scope): access the current element's scope in the console.
Closes #4884
2013-11-26 06:45:48 +00:00
sunnylost c2e45c769e refactor(angular.js): improve trim performance
According to Flagrant Badassery's blog
http://blog.stevenlevithan.com/archives/faster-trim-javascript
and this comparison http://jsperf.com/trim-function, this trim method is faster.

Closes #4406
2013-11-26 06:45:47 +00:00
Vojta Jina b08427dde9 chore(travis): add some more info for BrowserStack sessions 2013-11-25 18:04:35 -08:00
Vojta Jina ffd075b440 chore(travis): let's give BrowserStack a try
Switch the build to use BrowserStack instead of SauceLabs.

This also adds IE11 to our build.
2013-11-25 15:19:28 -08:00
Brian Ford 3fcd228441 chore: add script for updating bower repos 2013-11-25 13:09:50 -08:00
Pete Bacon Darwin 8383ecfcdf docs(CONTRIBUTING): add link to github-pr-helper 2013-11-25 20:18:36 +00:00
Matias Niemelä eed2333298 fix(ngAnimate): ensure animations are disabled upon bootstrap for structrual animations
Closes #5130
2013-11-25 15:00:50 -05:00
Pete Bacon Darwin a2809dacc4 docs(CONTRIBUTING): fix typo 2013-11-25 15:55:24 +00:00
Pete Bacon Darwin b837a31afa docs(CONTRIBUTING): highlight what makes a good issue submission 2013-11-25 14:33:51 +00:00
Pete Bacon Darwin 66b0fcd3c0 docs(CONTRIBUTING): consolidated submitting PRs sections 2013-11-25 14:15:24 +00:00
Matias Niemelä 2efe82309a fix($animate): ensure blocked keyframe animations are unblocked before the DOM operation
Closes #5106
2013-11-23 22:05:04 -05:00
Tobias Bosch a090400f09 fix(input): Support form auto complete on modern browser
Although modern browser support the "input" event, they still only fire
the "change" event when they auto complete form elements
other than the currently selected one.

Related to #1460
2013-11-22 17:02:21 -08:00
Brian Ford 84e0eea164 chore(docs): remove Disqus comments
We don't actively moderate these comments, and they range from
out of date, to inflammatory, to spam. Going forward, improvements
to the docs should be done via a PR, and questions should go on
StackOverflow where they can be curated and kept up to date by
AngularJS developers who help out there.
2013-11-22 16:27:05 -08:00
Igor Minar bcf12e70e5 chore: update copyright year in file headers 2013-11-22 13:16:23 -08:00
Igor Minar 1ca98b2c09 chore(release): start 1.2.3 unicorn-zapper iteration 2013-11-22 12:43:52 -08:00
107 changed files with 5373 additions and 3693 deletions
+1
View File
@@ -17,3 +17,4 @@ angular.xcodeproj
libpeerconnection.log
npm-debug.log
/tmp/
/scripts/bower/bower-*
+9 -5
View File
@@ -3,11 +3,16 @@ node_js:
- 0.10
env:
matrix:
- JOB=unit
- JOB=e2e
global:
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
- SAUCE_CONNECT_READY_FILE=/tmp/sauce-connect-ready
- BROWSER_STACK_USERNAME=VojtaJina
- BROWSER_STACK_ACCESS_KEY=HAfHZaypxAc3PEUrUU9a
- LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
before_script:
- mkdir -p $LOGS_DIR
@@ -16,11 +21,10 @@ before_script:
- grunt bower
- grunt bower
- grunt package-without-bower
- grunt ci-checks
- ./lib/sauce/sauce_connect_block.sh
- ./scripts/travis/wait_for_browser_provider.sh
script:
- ./travis_build.sh
- ./scripts/travis/build.sh
after_script:
- ./travis_print_logs.sh
- ./scripts/travis/print_logs.sh
+133
View File
@@ -1,3 +1,136 @@
<a name="1.2.5"></a>
# 1.2.5 singularity-expansion (2013-12-13)
## Bug Fixes
- **$compile:** allow literals in isolate scope references
([43072e38](https://github.com/angular/angular.js/commit/43072e3812e32b89b97ad03144577cba50d4b776),
[#5296](https://github.com/angular/angular.js/issues/5296))
- **angular-mocks:** use copy of mock data in $httpBackend
([f69dc162](https://github.com/angular/angular.js/commit/f69dc16241c8b631123ad0b09674f0a5e0ff32fe))
- **closure:** add missing FormController extern definitions
([1d5e18b0](https://github.com/angular/angular.js/commit/1d5e18b062c3e33b2a8d96aa58d905ed2cd48649),
[#5303](https://github.com/angular/angular.js/issues/5303))
- **ngInclude:** add template to DOM before linking other directives
([30a8b7d0](https://github.com/angular/angular.js/commit/30a8b7d0b5d4882c2bf3b20eb696a02f5b667726),
[#5247](https://github.com/angular/angular.js/issues/5247))
- **ngView:** add template to DOM before linking other directives
([f8944efe](https://github.com/angular/angular.js/commit/f8944efe70b81e02704df9b53ea2546c80c73d3b))
## Performance Improvements
- **$injector:** remove invoke optimization that doesn't work
([05e4fd34](https://github.com/angular/angular.js/commit/05e4fd3488b89e670c36869f18defe26deac2efa),
[#5388](https://github.com/angular/angular.js/issues/5388))
- **$resource:** use shallow copy instead of angular.copy
([fcd2a813](https://github.com/angular/angular.js/commit/fcd2a8131a3cb3e59a616bf31e61510b5c3a97d3),
[#5300](https://github.com/angular/angular.js/issues/5300))
- **a:** do not link when href or name exists in template
([f3de5b6e](https://github.com/angular/angular.js/commit/f3de5b6eac90baf649506072162f36dbc6d2f028),
[#5362](https://github.com/angular/angular.js/issues/5362))
- **jqLite:** implement and use the `empty` method in place of `html(‘’)`
([3410f65e](https://github.com/angular/angular.js/commit/3410f65e790a81d457b4f4601a1e760a6f8ede5e),
[#4457](https://github.com/angular/angular.js/issues/4457))
## Breaking Changes
- **angular-mocks:** due to [f69dc162](https://github.com/angular/angular.js/commit/f69dc16241c8b631123ad0b09674f0a5e0ff32fe),
some tests that rely on identity comparison rather than equality comparison in checking mock http responses will be broken,
since now each mock response is a copy of the original response. This is usually fixable by changing a `.toBe()` comparison
to `toEqual()` inside of tests.
<a name="1.2.4"></a>
# 1.2.4 wormhole-blaster (2013-12-06)
## Bug Fixes
- **$animate:**
- ensure animations work with directives that share a transclusion
([958d3d56](https://github.com/angular/angular.js/commit/958d3d56b1899a2cfc7b18c0292e5a1d8c64d0a5),
[#4716](https://github.com/angular/angular.js/issues/4716), [#4871](https://github.com/angular/angular.js/issues/4871), [#5021](https://github.com/angular/angular.js/issues/5021), [#5278](https://github.com/angular/angular.js/issues/5278))
- ensure ms durations are properly rounded
([93901bdd](https://github.com/angular/angular.js/commit/93901bdde4bb9f0ba114ebb33b8885808e1823e1),
[#5113](https://github.com/angular/angular.js/issues/5113), [#5162](https://github.com/angular/angular.js/issues/5162))
- **$compile:**
- update cloned elements if the template arrives after the cloning
([b0972a2e](https://github.com/angular/angular.js/commit/b0972a2e75909e41dbac6e4413ada7df2d51df3a))
- ensure the isolated local watch `lastValue` is always in sync
([2d0f6ccb](https://github.com/angular/angular.js/commit/2d0f6ccba896fe34141d6d4f59eef6fba580c5c2),
[#5182](https://github.com/angular/angular.js/issues/5182))
- **$rootScope:**
- ensure that when the $destroy event is broadcast on $rootScope that it does something
([d802ed1b](https://github.com/angular/angular.js/commit/d802ed1b3680cfc1751777fac465b92ee29944dc),
[#5169](https://github.com/angular/angular.js/issues/5169))
- ensure the phase is cleared within a digest if an exception is raised by a watcher
([d3c486dd](https://github.com/angular/angular.js/commit/d3c486dd6dfa8d5dca32a3e28aa685fb7260c878))
- **$sanitize:** don't rely on YARR regex engine executing immediately in order to prevent object mutation
([81b81856](https://github.com/angular/angular.js/commit/81b81856ee43d2876927c4e1f774affa87e99707),
[#5193](https://github.com/angular/angular.js/issues/5193), [#5192](https://github.com/angular/angular.js/issues/5192))
- **closure:** closure compiler shouldn't rename .defaults.transformRequest
([f01087f8](https://github.com/angular/angular.js/commit/f01087f802839637843115cbcf99702e09d866f6))
- **input:** ensure ngModelWatch() triggers second digest pass when appropriate
([b6d54393](https://github.com/angular/angular.js/commit/b6d5439343b9801f7f2a009d0de09cba9aa21a1d),
[#5258](https://github.com/angular/angular.js/issues/5258), [#5282](https://github.com/angular/angular.js/issues/5282))
- **isElement:** return boolean value rather than `truthy` value.
([2dbb6f9a](https://github.com/angular/angular.js/commit/2dbb6f9a54eb5ff5847eed11c85ac4cf119eb41c),
[#4519](https://github.com/angular/angular.js/issues/4519), [#4534](https://github.com/angular/angular.js/issues/4534))
- **jqLite:** ignore incompatible nodes on find()
([1169b544](https://github.com/angular/angular.js/commit/1169b5445691e1495354d235a3badf05240e3904),
[#4120](https://github.com/angular/angular.js/issues/4120))
- **ngInit:** evaluate ngInit before ngInclude
([0e50810c](https://github.com/angular/angular.js/commit/0e50810c53428f4c1f5bfdba9599df54cb7a6c6e),
[#5167](https://github.com/angular/angular.js/issues/5167), [#5208](https://github.com/angular/angular.js/issues/5208))
- **ngSanitize:** prefer textContent to innerText to avoid layout trashing
([bf1972dc](https://github.com/angular/angular.js/commit/bf1972dc1e8ffbeaddfa53df1d49bc5a2177f09c))
## Performance Improvements
- **$parse:** micro-optimization for ensureSafeObject function
([689dfb16](https://github.com/angular/angular.js/commit/689dfb167924a61aef444ce7587fb987d8080990),
[#5246](https://github.com/angular/angular.js/issues/5246))
- **Scope:** short-circuit after dirty-checking last dirty watcher
([d070450c](https://github.com/angular/angular.js/commit/d070450cd2b3b3a3aa34b69d3fa1f4cc3be025dd),
[#5272](https://github.com/angular/angular.js/issues/5272), [#5287](https://github.com/angular/angular.js/issues/5287))
<a name="1.2.3"></a>
# 1.2.3 unicorn-zapper (2013-11-27)
## Bug Fixes
- **$animate:**
- ensure blocked keyframe animations are unblocked before the DOM operation
([2efe8230](https://github.com/angular/angular.js/commit/2efe82309ac8ff4f67df8b6e40a539ea31e15804),
[#5106](https://github.com/angular/angular.js/issues/5106))
- ensure animations are disabled during bootstrap to prevent unwanted structural animations
([eed23332](https://github.com/angular/angular.js/commit/eed2333298412fbad04eda97ded3487c845b9eb9),
[#5130](https://github.com/angular/angular.js/issues/5130))
- **$sanitize:** use the same whitelist mechanism as `$compile` does
([33352348](https://github.com/angular/angular.js/commit/333523483f3ce6dd3177b697a5e5a7177ca364c8),
[#3748](https://github.com/angular/angular.js/issues/3748))
- **input:** react to form auto completion, through the `change` event, on modern browsers
([a090400f](https://github.com/angular/angular.js/commit/a090400f09d7993d102f527609879cdc74abae60),
[#1460](https://github.com/angular/angular.js/issues/1460))
- **$attrs:** add `$attrs.$attr` to externs so that it isn't renamed on js minification
([bcca8054](https://github.com/angular/angular.js/commit/bcca80548dde85ffe3838c943ba8e5c2deb1c721))
## Features
No new features in this release
## Breaking Changes
There are no breaking changes in this release (promise!)
<a name="1.2.2"></a>
# 1.2.2 consciousness-inertia (2013-11-22)
+108 -64
View File
@@ -29,32 +29,124 @@ duplication of work, and help you to craft the change so that it is successfully
project.
* **Small Changes** can be crafted and submitted to [GitHub Repository][github] as a Pull Request.
## Want a Doc Fix?
If you want to help improve the docs, it's a good idea to let others know what you're working on to
minimize duplication of effort. Before starting, check out the issue queue for [Milestone:Docs Only](https://github.com/angular/angular.js/issues?milestone=24&state=open).
Comment on an issue to let others know what you're working on, or create a new issue if your work
doesn't fit within the scope of any of the existing doc fix projects.
For large fixes, please build and test the documentation before submitting the PR to be sure you haven't
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.
## Submission Guidelines
### Submitting an Issue
Before you submit your issue follow the following guidelines:
* Search the archive first, it's likely that your question was already answered.
* A live example demonstrating the issue, will get an answer faster.
* Create one using [Plunker][plunker] or [JSFiddle][jsfiddle].
* If you get help, help others. Good karma rulez!
Before you submit your issue search the archive, maybe your question was already answered.
If your issue appears to be a bug, and hasn't been reported, open a new issue.
Help us to maximize the effort we can spend fixing issues and adding new
features, by not reporting duplicate issues.
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
* **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
[JSFiddle][jsfiddle]) or a unambiguous set of steps.
* **Related issues** - has a similar issue been reported before?
* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
causing the problem (line of code or commit)
Here is a great example of a well defined issue: https://github.com/angular/angular.js/issues/5069
**If you get help, help others. Good karma rulez!**
### Submitting a Pull Request
Before you submit your pull request follow the following guidelines:
Before you submit your pull request consider the following guidelines:
* Search GitHub for an open or closed Pull Request that relates to your submission. You don't want
to duplicate effort.
* Search [GitHub](https://github.com/angular/angular.js/pulls) for an open or closed Pull Request
that relates to your submission. You don't want to duplicate effort.
* Please sign our [Contributor License Agreement (CLA)](#signing-the-cla) before sending pull
requests. We cannot accept code without this.
* Make your changes in a new git branch
```shell
git checkout -b my-fix-branch master
```
* Create your patch, including appropriate test cases.
* Follow our Coding Rules
* Follow our Git Commit Guidelines
* Build your changes locally and on Travis (by pushing to GitHub) to ensure all the tests pass.
* Sign the Contributor License Agreement (CLA). We cannot accept code without this.
* Commit your changes and create a descriptive commit message (the
commit message is used to generate release notes, please check out our
[commit message conventions](#commit-message-format) and our commit message presubmit hook
`validate-commit-msg.js`):
```shell
git commit -a
```
* Build your changes locally to ensure all the tests pass
```shell
grunt test
```
* Push your branch to Github:
```shell
git push origin my-fix-branch
```
* In Github, send a pull request to `angular:master`.
* If we suggest changes then you can modify your branch, rebase and force a new push to your GitHub
repository to update the Pull Request.
repository to update the Pull Request:
```shell
git rebase master -i
git push -f
```
That's it! Thank you for your contribution!
When the patch is reviewed and merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
* Delete the remote branch on Github:
```shell
git push origin --delete my-fix-branch
```
* Check out the master branch:
```shell
git checkout master -f
```
* Delete the local branch:
```shell
git branch -D my-fix-branch
```
* Update your master with the latest upstream version:
```shell
git pull --ff upstream master
```
### GitHub Pull Request Helper
We track Pull Requests by attaching labels and assigning to milestones. For some reason GitHub
does not provide a good UI for managing labels on Pull Requests (unlike Issues). We have developed
a simple Chrome Extension that enables you to view (and manage if you have permission) the labels
on Pull Requests. You can get the extension from the Chrome WebStore -
[GitHub PR Helper][github-pr-helper]
## Coding Rules
To ensure consistency throughout the source code, keep these rules in mind as you are working:
@@ -106,6 +198,7 @@ Must be one of the following:
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
semi-colons, etc)
* **refactor**: A code change that neither fixes a bug or adds a feature
* **perf**: A code change that improves performance
* **test**: Adding missing tests
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
generation
@@ -146,56 +239,6 @@ You can find out more detailed information about contributing in the
[AngularJS documentation][contributing].
## Submitting Your Changes
To create and submit a change:
1. Please sign our [Contributor License Agreement (CLA)](#signing-the-cla) before sending pull
requests.
2. Create and checkout a new branch off the master branch for your changes:
```shell
git checkout -b my-fix-branch master
```
3. Create your patch, including appropriate test cases.
4. Commit your changes and create a descriptive commit message (the commit message is used to
generate release notes, please check out our [commit message conventions](#commit-message-format)
and our commit message presubmit hook `validate-commit-msg.js`):
```shell
git commit -a
```
5. Push your branch to Github:
```shell
git push origin my-fix-branch
```
6. In Github, send a pull request to `angular:master`.
That's it! Thank you for your contribution!
When the patch is reviewed and merged, you can safely delete your branch and pull the changes
from the main (upstream) repository:
```shell
# Delete the remote branch on Github:
git push origin :my-fix-branch
# Check out the master branch:
git checkout master
# Delete the local branch:
git branch -D my-fix-branch
# Update your master with the latest upstream version:
git pull --ff upstream master
```
[github]: https://github.com/angular/angular.js
[Google Closure I18N library]: https://code.google.com/p/closure-library/source/browse/closure/goog/i18n/
@@ -214,3 +257,4 @@ git pull --ff upstream master
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
+4 -11
View File
@@ -106,45 +106,38 @@ module.exports = function(grunt) {
},
jshint: {
options: {
jshintrc: true,
},
ng: {
files: { src: files['angularSrc'] },
options: { jshintrc: 'src/.jshintrc' }
},
ngAnimate: {
files: { src: 'src/ngAnimate/**/*.js' },
options: { jshintrc: 'src/ngAnimate/.jshintrc' }
},
ngCookies: {
files: { src: 'src/ngCookies/**/*.js' },
options: { jshintrc: 'src/ngCookies/.jshintrc' }
},
ngLocale: {
files: { src: 'src/ngLocale/**/*.js' },
options: { jshintrc: 'src/ngLocale/.jshintrc' }
},
ngMock: {
files: { src: 'src/ngMock/**/*.js' },
options: { jshintrc: 'src/ngMock/.jshintrc' }
},
ngResource: {
files: { src: 'src/ngResource/**/*.js' },
options: { jshintrc: 'src/ngResource/.jshintrc' }
},
ngRoute: {
files: { src: 'src/ngRoute/**/*.js' },
options: { jshintrc: 'src/ngRoute/.jshintrc' }
},
ngSanitize: {
files: { src: 'src/ngSanitize/**/*.js' },
options: { jshintrc: 'src/ngSanitize/.jshintrc' }
},
ngScenario: {
files: { src: 'src/ngScenario/**/*.js' },
options: { jshintrc: 'src/ngScenario/.jshintrc' }
},
ngTouch: {
files: { src: 'src/ngTouch/**/*.js' },
options: { jshintrc: 'src/ngTouch/.jshintrc' }
}
},
@@ -178,7 +171,7 @@ module.exports = function(grunt) {
},
mocks: {
dest: 'build/angular-mocks.js',
src: files['angularModules']['ngMock'],
src: util.wrap(files['angularModules']['ngMock'], 'module'),
strict: false
},
sanitize: {
+61
View File
@@ -0,0 +1,61 @@
# Triage new issues/PRs on github
This document shows the steps the Angular team is using to triage issues.
The labels are used later on for planning releases.
## Tips ##
* install [github pr helper extension](https://github.com/petebacondarwin/github-pr-helper) and become 356% more productive
* Label "resolution:*"
* these tags can be used for labeling a closed issue/PR with a reason why it was closed. (we can add reasons as we need them, right there are only a few rejection reasons. it doesn't make sense to label issues that were fixed or prs that were merged)
## Automatic processing ##
We have automatic tools (e.g. Mary Poppins) that automatically add comments / labels to issues and PRs.
The following is done automatically and should not be done manually:
* Label "cla: yes" or "cla: no" for pull requests
## Process ##
1. Open list of [non triaged issues](https://github.com/angular/angular.js/issues?direction=desc&milestone=none&page=1&sort=created&state=open)
1. Assign yourself: Pick an issue that is not assigned to anyone and assign it to you
1. Assign milestone:
* "Docs only" milestone - for documentation PR -> **Done**.
* Current/next milestone - regressions
* 1.2.x - everything else
1. Label "GH: *" (to be automated via Mary Poppins)
* PR - issue is a PR
* issue - otherwise
1. Bugs:
* Label "Type: Bug"
* Label "Type: Regression" - if the bug is a regression
* Duplicate? - Check if there are comments pointing out that this is a dupe, if they do exist verify that this is indeed a dupe and close it and go to the last step
* Reproducible? - Steps to reproduce the bug are clear, if not ask for clarification (ideally plunker or fiddle)
* Reproducible on master? - http://code.angularjs.org/snapshot/
1. Non bugs:
* Label "Type: Feature" or "Type: Chore" or "Type: Perf"
* Label "needs: breaking change" - if needed
* Label "needs: public api" - if a new public api is needed
* Understandable? - verify if the description of the request is clear. if not ask for clarification
* Goals of angular core? - Often new features should be implemented as a third-party module rather than an addition to the core.
1. Label "component: *"
* In rare cases, it's ok to have multiple components.
1. Label "impact: *"
* small - obscure issue affecting one or handful of developers
* medium - impacts some usage patterns
* large - impacts most or all of angular apps
1. Label "complexity: *"
* small - trivial change
* medium - non-trivial but straightforward change
* large - changes to many components in angular or any changes to $compile, ngRepeat or other "fun" components
1. Label "PRs welcome" for "GH: issue"
* if complexity is small or medium and the problem as well as solution are well captured in the issue
1. Label "origin: google" for issues from Google
1. Label "high priority" for security issues, major performance regressions or memory leaks
1. Unassign yourself from the issue
+1
View File
@@ -27,6 +27,7 @@ angularFiles = {
'src/ng/parse.js',
'src/ng/q.js',
'src/ng/rootScope.js',
'src/ng/sanitizeUri.js',
'src/ng/sce.js',
'src/ng/sniffer.js',
'src/ng/timeout.js',
+3 -1
View File
@@ -142,6 +142,7 @@ var writeChangelog = function(stream, commits, version) {
var sections = {
fix: {},
feat: {},
perf: {},
breaks: {}
};
@@ -169,6 +170,7 @@ var writeChangelog = function(stream, commits, version) {
stream.write(util.format(HEADER_TPL, version, version, currentDate()));
printSection(stream, 'Bug Fixes', sections.fix);
printSection(stream, 'Features', sections.feat);
printSection(stream, 'Performance Improvements', sections.perf);
printSection(stream, 'Breaking Changes', sections.breaks, false);
}
@@ -186,7 +188,7 @@ var getPreviousTag = function() {
var generate = function(version, file) {
getPreviousTag().then(function(tag) {
console.log('Reading git log since', tag);
readGitLog('^fix|^feat|BREAKING', tag).then(function(commits) {
readGitLog('^fix|^feat|^perf|BREAKING', tag).then(function(commits) {
console.log('Parsed', commits.length, 'commits');
console.log('Generating changelog to', file || 'stdout', '(', version, ')');
writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
-80
View File
@@ -1,80 +0,0 @@
<a name="v1.0.0rc3"></a>
# v1.0.0rc3 (2012-03-27)
## Bug Fixes
- **$compile:**
- create new (isolate) scopes for directives on root elements ([5390fb37](https://github.com/angular/angular.js/commit/5390fb37d2c01937922613fc57df4986af521787), closes [#817](https://github.com/angular/angular.js/issues/817))
- don't touch static element attributes ([9cb2195e](https://github.com/angular/angular.js/commit/9cb2195e61a78e99020ec19d687a221ca88b5900))
- Merge interpolated css class when replacing an element ([f49eaf8b](https://github.com/angular/angular.js/commit/f49eaf8bf2df5f4e0e82d6c89e849a4f82c8d414))
- **$http:**
- don't send Content-Type header when no data ([1a5bebd9](https://github.com/angular/angular.js/commit/1a5bebd927ecd22f9c34617642fdf58fe3f62efb), closes [#749](https://github.com/angular/angular.js/issues/749))
- **$log:**
- avoid console.log.apply calls in IE ([15213ec2](https://github.com/angular/angular.js/commit/15213ec212769837cb2b7e781ffc5bfd598d27ca), closes [#805](https://github.com/angular/angular.js/issues/805))
- **$resource:**
- support escaping of ':' in resource url ([6d6f8753](https://github.com/angular/angular.js/commit/6d6f875345e01f2c6c63ef95164f6f39e923da15))
- **compiler:**
- allow transclusion of root elements ([9918b748](https://github.com/angular/angular.js/commit/9918b748be01266eb10db39d51b4d3098d54ab66))
- **e2e runner:**
- fix typo that caused errors on IE8 ([ee5a5352](https://github.com/angular/angular.js/commit/ee5a5352fd4b94cedee6ef20d4bf2d43ce77e00b), closes [#806](https://github.com/angular/angular.js/issues/806))
- **forEach:**
- should ignore prototypically inherited properties ([8d7e6948](https://github.com/angular/angular.js/commit/8d7e6948496ff26ef1da8854ba02fcb8eebfed61), closes [#813](https://github.com/angular/angular.js/issues/813))
- **forms:**
- Remove double registering of form ([1faafa31](https://github.com/angular/angular.js/commit/1faafa31582c4e9413f48dc7d12f5b681f9fe9fd))
- Set ng-valid/ng-invalid correctly ([08bfea18](https://github.com/angular/angular.js/commit/08bfea183a850b29da270eac47f80b598cbe600f))
- **init:**
- use jQuery#ready for init if available ([cb2ad9ab](https://github.com/angular/angular.js/commit/cb2ad9abf24e6f855cc749efe3155bd7987ece9d), closes [#818](https://github.com/angular/angular.js/issues/818))
- **json:**
- added support for iso8061 timezone ([5ac14f63](https://github.com/angular/angular.js/commit/5ac14f633a69f49973b5512780c6ec7752405967))
- **matchers.toHaveClass:**
- Correct reference to angular.mock.dump ([f701ce08](https://github.com/angular/angular.js/commit/f701ce08f9d63be05fc3b92f57ad473e1e749b2d))
- **ng-switch:**
- properly destroy child scopes ([2315d9b3](https://github.com/angular/angular.js/commit/2315d9b3610994b36c44e4a97fb1427d59471ce8))
- **ngDocSpec:**
- fix broken tests ([53b6f522](https://github.com/angular/angular.js/commit/53b6f522a56eea314cbd084816e08f24b2c7879f))
- **ngForm:**
- alias name||ngForm ([823adb23](https://github.com/angular/angular.js/commit/823adb231995e917bc060bfa49453e2a96bac2b6))
- **ngRepeat:**
- correct variable reference in error message ([935c1018](https://github.com/angular/angular.js/commit/935c1018da05dbf3124b2dd33619c4a3c82d7a2a))
- **ngView:**
- controller not published ([21e74c2d](https://github.com/angular/angular.js/commit/21e74c2d2e8e985b23711785287feb59965cbd90))
- **q:**
- resolve all of nothing to nothing ([ac75079e](https://github.com/angular/angular.js/commit/ac75079e2113949d5d64adbcf23d56f3cf295d41))
- **select:**
- multiselect failes to update view on selection insert ([6ecac8e7](https://github.com/angular/angular.js/commit/6ecac8e71a84792a434d21db2c245b3648c55f18))
## Features
- **$compile:**
- do not interpolate boolean attributes, rather evaluate them ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- **$controller:**
- support controller registration via $controllerProvider ([d54dfecb](https://github.com/angular/angular.js/commit/d54dfecb00fba41455536c5ddd55310592fdaf84))
- **$route:**
- when matching consider trailing slash as optional ([a4fe51da](https://github.com/angular/angular.js/commit/a4fe51da3ba0dc297ecd389e230d6664f250c9a6), closes [#784](https://github.com/angular/angular.js/issues/784))
- **assertArgFn:**
- should support array annotated fns ([4b8d9260](https://github.com/angular/angular.js/commit/4b8d926062eb4d4483555bdbdec4656f585ab40b))
- **http:**
- added params parameter ([73c85930](https://github.com/angular/angular.js/commit/73c8593077155a9f2e8ef42efd4c497eba0bef4f))
- **injector:**
- infer _foo_ as foo ([f13dd339](https://github.com/angular/angular.js/commit/f13dd3393dfb7a33565c9360342c193bc0bddcb6))
- **input.radio:**
- Allow value attribute to be interpolated ([ade6c452](https://github.com/angular/angular.js/commit/ade6c452753145c84884d17027a7865bf4b34b0c))
- **jqLite:**
- make injector() and scope() work with the document object ([5fdab52d](https://github.com/angular/angular.js/commit/5fdab52dd7c269f99839f4fa6b5854d9548269fa))
- add .controller() method ([6c5a05ad](https://github.com/angular/angular.js/commit/6c5a05ad49a1e083570c3dfe331403398f899dbe))
- **ngValue:**
- allow radio inputs to have non string values ([09e175f0](https://github.com/angular/angular.js/commit/09e175f02cca0f4a295fd0c9b980cd8f432e722b), closes [#816](https://github.com/angular/angular.js/issues/816))
- **scope:**
- broadcast $destroy event on scope destruction ([9b1aff90](https://github.com/angular/angular.js/commit/9b1aff905b638aa274a5fc8f88662df446d374bd))
- **scope.$eval:**
- Allow passing locals to the expression ([192ff61f](https://github.com/angular/angular.js/commit/192ff61f5d61899e667c6dbce4d3e6e399429d8b))
## Breaking Changes
- boolean attrs are evaluated rather than interpolated ([a08cbc02](https://github.com/angular/angular.js/commit/a08cbc02e78e789a66e9af771c410e8ad1646e25))
- ng-bind-attr directive removed ([55027132](https://github.com/angular/angular.js/commit/55027132f3d57e5dcf94683e6e6bd7b0aae0087d))
- any app that depends on this service and its fallback to Modernizr, please ([aaedefb9](https://github.com/angular/angular.js/commit/aaedefb92e6bec6626e173e5155072c91471596a))
+56 -2
View File
@@ -225,6 +225,11 @@ angular.uppercase = function(s) {};
*/
angular.Attributes;
/**
* @type {Object.<string, string>}
*/
angular.Attributes.$attr;
/**
* @param {string} name
* @return {string}
@@ -1048,6 +1053,10 @@ angular.$http;
*/
angular.$http.Config;
angular.$http.Config.transformRequest;
angular.$http.Config.transformResponse;
// /**
// * This extern is currently incomplete as delete is a reserved word.
// * To use delete, index $http.
@@ -1154,6 +1163,13 @@ angular.$http.HttpPromise.error = function(callback) {};
*/
angular.$http.Response;
angular.$HttpProvider;
/**
* @type {angular.$http.Config}
*/
angular.$HttpProvider.defaults;
/******************************************************************************
* $injector Service
*****************************************************************************/
@@ -1425,6 +1441,11 @@ angular.NgModelController.prototype.$viewValue;
*/
angular.FormController = function() {};
/**
* @param {*} control
*/
angular.FormController.prototype.$addControl = function(control) {};
/**
* @type {boolean}
*/
@@ -1440,11 +1461,39 @@ angular.FormController.prototype.$error;
*/
angular.FormController.prototype.$invalid;
/**
* @type {string}
*/
angular.FormController.prototype.$name;
/**
* @type {boolean}
*/
angular.FormController.prototype.$pristine;
/**
* @param {*} control
*/
angular.FormController.prototype.$removeControl = function(control) {};
/**
* @type {function()}
*/
angular.FormController.prototype.$setDirty = function() {};
/**
* @type {function()}
*/
angular.FormController.prototype.$setPristine = function() {};
/**
* @param {string} validationToken
* @param {boolean} isValid
* @param {*} control
*/
angular.FormController.prototype.$setValidity = function(
validationToken, isValid, control) {};
/**
* @type {boolean}
*/
@@ -1578,6 +1627,7 @@ angular.$q.when = function(value) {};
* @typedef {{
* resolve: function(*=),
* reject: function(*=),
* notify: function(*=),
* promise: angular.$q.Promise
* }}
*/
@@ -1589,6 +1639,9 @@ angular.$q.Deferred.resolve = function(opt_value) {};
/** @param {*=} opt_reason */
angular.$q.Deferred.reject = function(opt_reason) {};
/** @param {*=} opt_value */
angular.$q.Deferred.notify = function(opt_value) {};
/** @type {angular.$q.Promise} */
angular.$q.Deferred.promise;
@@ -1689,7 +1742,8 @@ angular.$routeProvider.when = function(path, route) {};
* resolve: (Object.<string, (
* string|Function|Array.<string|Function>|angular.$q.Promise
* )>|undefined),
* redirectTo: (string|function()|undefined),
* redirectTo: (
* string|function(Object.<string>, string, Object): string|undefined),
* reloadOnSearch: (boolean|undefined)
* }}
*/
@@ -1712,7 +1766,7 @@ angular.$routeProvider.Params.templateUrl;
*/
angular.$routeProvider.Params.resolve;
/** @type {string|function()} */
/** @type {string|function(Object.<string>, string, Object): string} */
angular.$routeProvider.Params.redirectTo;
/** @type {boolean} */
+1 -1
View File
@@ -5,7 +5,7 @@ describe('Docs Annotations', function() {
var body;
beforeEach(function() {
body = angular.element(document.body);
body.html('');
body.empty();
});
var normalizeHtml = function(html) {
+1 -1
View File
@@ -28,7 +28,7 @@ function escape(text) {
function setHtmlIe8SafeWay(element, html) {
var newElement = angular.element('<pre>' + html + '</pre>');
element.html('');
element.empty();
element.append(newElement.contents());
return element;
}
+2 -2
View File
@@ -3,9 +3,9 @@
@fullName Orphan ngTransclude Directive
@description
Occurs when an `ngTransclude` occurs without a transcluded ancesstor element.
Occurs when an `ngTransclude` occurs without a transcluded ancestor element.
This error often occurs when you have forgotten to set `transclude: true` in some directive definition, and then used `ngTranslude` in the driective's template.
This error often occurs when you have forgotten to set `transclude: true` in some directive definition, and then used `ngTransclude` in the directive's template.
To resolve, either remove the offending `ngTransclude` or check that `transclude: true` is included in the intended directive definition.
+5 -1
View File
@@ -190,6 +190,7 @@ This should help give you an idea of what Angular does internally.
<pre>
var $compile = ...; // injected into your code
var scope = ...;
var parent = ...; // DOM element where the compiled template can be appended
var html = '<div ng-bind="exp"></div>';
@@ -200,7 +201,10 @@ This should help give you an idea of what Angular does internally.
var linkFn = $compile(template);
// Step 3: link the compiled template with the scope.
linkFn(scope);
var element = linkFn(scope);
// Step 4: Append to DOM (optional)
parent.appendChild(element);
</pre>
### The difference between Compile and Link
+1 -1
View File
@@ -270,7 +270,7 @@ When Angular starts, it will use the configuration of the module with the name d
including the configuration of all modules that this module depends on.
In the example above:
The template contains the directive `ng-app="invoice"`. This tells Angular
The template contains the directive `ng-app="invoice2"`. This tells Angular
to use the `invoice` module as the main module for the application.
The code snippet `angular.module('invoice', ['finance'])` specifies that the `invoice` module depends on the
`finance` module. By this, Angular uses the `InvoiceController` as well as the `currencyConverter` service.
@@ -222,7 +222,7 @@ var pc = new PasswordCtrl();
input.val('abc');
pc.grade();
expect(span.text()).toEqual('weak');
$('body').html('');
$('body').empty();
</pre>
In angular the controllers are strictly separated from the DOM manipulation logic and this results in
+1 -1
View File
@@ -146,7 +146,7 @@ of service names to inject.
var MyController = function(renamed$scope, renamedGreeter) {
...
}
MyController.$inject = ['$scope', 'greeter'];
MyController['$inject'] = ['$scope', 'greeter'];
</pre>
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.
+1 -1
View File
@@ -18,7 +18,7 @@ It might be tempting to think of Angular view expressions as JavaScript expressi
not entirely correct, since Angular does not use a JavaScript `eval()` to evaluate expressions.
You can think of Angular expressions as JavaScript expressions with following differences:
* **Attribute Evaluation:** evaluation of all properties are against the scope, doing the
* **Attribute Evaluation:** evaluation of all properties are against the scope doing the
evaluation, unlike in JavaScript where the expressions are evaluated against the global
`window`.
+8 -3
View File
@@ -115,9 +115,14 @@ This ensures that the user is not distracted with an error until after interacti
A form is an instance of {@link api/ng.directive:form.FormController FormController}.
The form instance can optionally be published into the scope using the `name` attribute.
Similarly, control is an instance of {@link api/ng.directive:ngModel.NgModelController NgModelController}.
The control instance can similarly be published into the form instance using the `name` attribute.
This implies that the internal state of both the form and the control is available for binding in the view using the standard binding primitives.
Similarly, an input control that has the {@link api.ng.directive:ng-model} directive holds an
instance of {@link api/ng.directive:ngModel.NgModelController NgModelController}.
Such a control instance can be published as a property of the form instance using the `name` attribute
on the input control. The name attribute specifies the name of the property on the form instance.
This implies that the internal state of both the form and the control is available for binding in
the view using the standard binding primitives.
This allows us to extend the above example with these features:
+6 -46
View File
@@ -7,19 +7,14 @@
Everything you need to know about AngularJS
* {@link guide/introduction What is AngularJS?}
* {@link guide/concepts Conceptual Overview}
## Tutorials
* {@link tutorial/index Official AngularJS Tutorial}
* [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/)
* [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c)
* [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM)
* [For folks with jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background)
## Core Concepts
@@ -29,63 +24,44 @@ Everything you need to know about AngularJS
In Angular applications, you move the job of filling page templates with data from the server to the client. The result is a system better structured for dynamic page updates. Below are the core features you'll use.
* {@link guide/databinding Data binding}
* {@link guide/expression Expressions}
* {@link guide/directive Directives}
* {@link api/ngRoute.$route Views and routes (see the example)}
* {@link guide/filter Filters}
* {@link guide/forms Forms} and [Concepts of AngularJS Forms](http://mrbool.com/the-concepts-of-angularjs-forms/29117)
### Application Structure
* **Blog post: **[When to use directives, controllers or services](http://kirkbushell.me/when-to-use-directives-controllers-or-services-in-angular/)
* **App wiring:** {@link guide/di Dependency injection}
* **Exposing model to templates:** {@link guide/scope Scopes}
* **Communicating with servers:** {@link api/ng.$http $http}, {@link api/ngResource.$resource $resource}
### Other AngularJS Features
* **Animation:** {@link guide/animations Core concepts}, {@link api/ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
* **Security:** {@link api/ng.$sce Strict Contextual Escaping}, {@link api/ng.directive:ngCsp Content Security Policy}, {@link api/ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link api/ng.filter:date date filter}, {@link api/ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
* **Mobile:** {@link api/ngTouch Touch events}
### Testing
* **Unit testing:** [Using Karma (video)](http://www.youtube.com/watch?v=YG5DEzaQBIc), {@link guide/dev_guide.unit-testing Unit testing}, {@link guide/dev_guide.services.testing_services Testing services}, [Karma in Webstorm](http://blog.jetbrains.com/webstorm/2013/10/running-javascript-tests-with-karma-in-webstorm-7/)
* **Scenario testing:** [Protractor](https://github.com/angular/protractor)
## Specific Topics
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
* **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html)
## Tools
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
* **Workflow:** [Yeoman.io](https://github.com/yeoman/generator-angular) and [Angular Yeoman Tutorial](http://www.sitepoint.com/kickstart-your-angularjs-development-with-yeoman-grunt-and-bower/)
## Complementary Libraries
@@ -93,37 +69,26 @@ 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://pascalprecht.github.io/angular-translate/), [angular-gettext](http://angular-gettext.rocketeer.be/)
* **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/)
## Deployment
### General
### General
* **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ngmin automation tool](http://www.thinkster.io/pick/XlWneEZCqY/angularjs-ngmin)
* **Tracking:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm)
* **SEO:** [By hand](http://www.yearofmoo.com/2012/11/angularjs-and-seo.html), [prerender.io](http://prerender.io/), [Brombone](http://www.brombone.com/), [SEO.js](http://getseojs.com/), [SEO4Ajax](http://www.seo4ajax.com/)
### Server-Specific
* **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)
* **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)
* **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)
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
## Learning Resources
@@ -137,18 +102,18 @@ This is a short list of libraries with specific support and documentation for wo
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
###Videos:
* [egghead.io](http://egghead.io/),
* [egghead.io](http://egghead.io/)
* [Angular on YouTube](http://youtube.com/angularjs)
###Courses
* **Free on-line:**
### Courses
* **Free online:**
[thinkster.io](http://thinkster.io),
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
* **Paid on-line:**
* **Paid online:**
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
* **Paid on-site:**
* **Paid onsite:**
[angularbootcamp.com](http://angularbootcamp.com/)
## Getting Help
@@ -156,19 +121,14 @@ This is a short list of libraries with specific support and documentation for wo
The recipe for getting help on your unique issue is to create an example that could work (even if it doesn't) in a shareable example on [Plunker](http://plnkr.co/), [JSFiddle](http://jsfiddle.net/), or similar site and then post to one of the following:
* [Stackoverflow.com](http://stackoverflow.com/search?q=angularjs)
* [AngularJS mailing list](https://groups.google.com/forum/#!forum/angular)
* [AngularJS IRC channel](http://webchat.freenode.net/?channels=angularjs&uio=d4)
## Social Channels
* **Daily updates:** [Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs)
* **Weekly newsletter:** [ng-newsletter](http://www.ng-newsletter.com/)
* **Meetups: **[meetup.com](http://www.meetup.com/find/?keywords=angularJS&radius=Infinity&userFreeform=San+Francisco%2C+CA&mcId=z94108&mcName=San+Francisco%2C+CA&sort=member_count&eventFilter=mysugg)
* **Official news and releases: **[AngularJS Blog](http://blog.angularjs.org/)
## Contributing to AngularJS
+4
View File
@@ -590,6 +590,10 @@ See [79223eae](https://github.com/angular/angular.js/commit/79223eae502283889334
## Underscore-prefixed/suffixed properties are non-bindable
<div class="alert alert-info">
<p>**Reverted**: This breaking change has been reverted in 1.2.1, and so can be ignored if you're using **version 1.2.1 or higher**</p>
</div>
This change introduces the notion of "private" properties (properties
whose names begin and/or end with an underscore) on the scope chain.
These properties will not be available to Angular expressions (i.e. {{
+1 -1
View File
@@ -110,7 +110,7 @@ myApp.factory('clientId', function clientIdFactory() {
But given that the token is just a string literal, sticking with the Value recipe is still more
appropriate as it makes the code easier to follow.
Let's say, however, that we would also like create a service that computes a token used for
Let's say, however, that we would also like to create a service that computes a token used for
authentication against a remote API. This token will be called 'apiToken' and will be computed
based on the `clientId` value and a secret stored in browser's local storage:
+1 -1
View File
@@ -177,7 +177,7 @@ To examine the scope in the debugger:
2. The debugger allows you to access the currently selected element in the console as `$0`
variable.
3. To retrieve the associated scope in console execute: `angular.element($0).scope()`
3. To retrieve the associated scope in console execute: `angular.element($0).scope()` or just type $scope
## Scope Events Propagation
+1 -1
View File
@@ -23,7 +23,7 @@ for how to contribute your own code to AngularJS.
Before you can build AngularJS, you must install and configure the following dependencies on your
machine:
* {@link http://git-scm.com/ Git}: The {@link http://help.github.com/mac-git-installation Github Guide to
* {@link http://git-scm.com/ Git}: The {@link https://help.github.com/articles/set-up-git Github Guide to
Installing Git} is a good source of information.
* {@link http://nodejs.org Node.js}: We use Node to generate the documentation, run a
+2 -2
View File
@@ -26,7 +26,7 @@ angular-seed, and run the application in the browser.
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node ./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
@@ -54,7 +54,7 @@ angular-seed, and run the application in the browser.
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
+5 -1
View File
@@ -184,7 +184,11 @@ http://pivotal.github.com/jasmine/ Jasmine home page} and at the {@link
http://pivotal.github.io/jasmine/ Jasmine docs}.
The angular-seed project is pre-configured to run all unit tests using {@link
http://karma-runner.github.io/ Karma}. To run the test, do the following:
http://karma-runner.github.io/ Karma}. Ensure that the necessary karma plugins are installed.
You can do this by issuing `npm install` into your terminal.
To run the test, do the following:
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
`./scripts/test.sh` to start the Karma server (the config file necessary to start the server
+3 -3
View File
@@ -127,8 +127,8 @@ end-to-end tests! Use `./scripts/e2e-test.sh` script for that. End-to-end tests
with unit tests, Karma will exit after the test run and will not automatically rerun the test
suite on every file change. To rerun the test suite, execute the `e2e-test.sh` script again.
Note: You must ensure you've installed karma-ng-scenario prior to running the `e2e-test.sh` script.
You can do this by issuing `npm install karma-ng-scenario` into your terminal.
Note: You must ensure you've installed the karma-ng-scenario framework plugin prior to running the
`e2e-test.sh` script. You can do this by issuing `npm install` into your terminal.
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
@@ -154,7 +154,7 @@ really is that easy to set up any functional, readable, end-to-end test.
`ngController` declaration to the HTML element because it is the common parent of both the body
and title elements:
<html ng-app ng-controller="PhoneListCtrl">
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">
Be sure to __remove__ the `ng-controller` declaration from the body element.
+1 -1
View File
@@ -64,7 +64,7 @@ We also added phone images next to each record using an image tag with the {@lin
api/ng.directive:ngSrc ngSrc} directive. That directive prevents the
browser from treating the angular `{{ expression }}` markup literally, and initiating a request to
invalid url `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had only
specified an attribute binding in a regular `src` attribute (`<img class="diagram" src="{{phone.imageUrl}}">`).
specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
+2 -2
View File
@@ -258,7 +258,7 @@ to various URLs and verify that the correct view was rendered.
<pre>
...
it('should redirect index.html to index.html#/phones', function() {
browser().navigateTo('../../app/index.html');
browser().navigateTo('app/index.html');
expect(browser().location().url()).toBe('/phones');
});
...
@@ -266,7 +266,7 @@ to various URLs and verify that the correct view was rendered.
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
browser().navigateTo('app/index.html#/phones/nexus-s');
});
+1 -1
View File
@@ -120,7 +120,7 @@ Angular's server}.
<button ng-click="hello('Elmo')">Hello</button>
to the `phone-details.html` template.
to the `phone-detail.html` template.
<div style="display: none">
TODO!
+6 -6
View File
@@ -43,7 +43,7 @@ __`app/index.html`.__
<pre>
...
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<!-- required module to enable animation support in AngularJS -->
<script src="lib/angular/angular-animate.js"></script>
@@ -56,6 +56,10 @@ __`app/index.html`.__
...
</pre>
<div class="alert alert-error">
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
</div>
Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`).
But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before
with `ngResource`.
@@ -153,7 +157,7 @@ __`app/css/animations.css`__
</pre>
As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are
inserted info and removed from the list:
inserted into and removed from the list:
* The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page.
* The `ng-move` class is applied when items are moved around in the list.
@@ -383,10 +387,6 @@ isn't required to do JavaScript animations with AngularJS, but we're going to us
your own JavaScript animation library is beyond the scope of this tutorial. For more on
`jQuery.animate`, see the {@link http://api.jquery.com/animate/ jQuery documentation}.
<div class="alert alert-error">
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
</div>
The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed
on the element that contains the class we registered, which is in this case `.phone`. When the `.active`
class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will
+21
View File
@@ -144,6 +144,27 @@
.content h4,
.content h5 {
margin-top: 1em;
letter-spacing: -0.06em;
}
.content h2 {
font-size: 36px;
margin-bottom: .5em;
}
.content h3 {
font-size: 24px;
border-top: 1px solid #eee;
padding-top: .5em;
}
.content h4 {
font-size: 16px;
margin-top: 1.5em;
}
.content ul {
margin-top: .5em;
}
ul.parameters > li > p,
+3 -4
View File
@@ -335,9 +335,8 @@
<div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content slide-reveal"></div>
<div id="disqus" class="disqus">
<h2>Discussion</h2>
<div id="disqus_thread" class="content-panel-content"></div>
<div class="alert alert-info">
<a href="http://blog.angularjs.org/2013/11/farewell-disqus.html">Where did Disqus go?</a>
</div>
</div>
</div>
@@ -365,7 +364,7 @@
<footer class="footer">
<div class="container">
<p class="pull-right"><a href="#">Back to top</a></p>
<p class="pull-right"><a back-to-top href="#">Back to top</a></p>
<p>
Super-powered by Google ©2010-2012
+19 -28
View File
@@ -273,10 +273,10 @@ docsApp.directive.docTutorialNav = function(templateMerge) {
element.addClass('btn-group');
element.addClass('tutorial-nav');
element.append(templateMerge(
'<li class="btn btn-primary"><a href="tutorial/{{prev}}"><i class="icon-step-backward"></i> Previous</a></li>\n' +
'<li class="btn btn-primary"><a href="http://angular.github.com/angular-phonecat/step-{{seq}}/app"><i class="icon-play"></i> Live Demo</a></li>\n' +
'<li class="btn btn-primary"><a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><i class="icon-search"></i> Code Diff</a></li>\n' +
'<li class="btn btn-primary"><a href="tutorial/{{next}}">Next <i class="icon-step-forward"></i></a></li>', props));
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="icon-step-backward"></i> Previous</li></a>\n' +
'<a href="http://angular.github.com/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="icon-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="icon-search"></i> Code Diff</li></a>\n' +
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="icon-step-forward"></i></li></a>', props));
}
};
};
@@ -372,6 +372,21 @@ docsApp.directive.errorDisplay = ['$location', 'errorLinkFilter', function ($loc
}];
/**
* backToTop Directive
* @param {Function} $anchorScroll
*
* @description Ensure that the browser scrolls when the anchor is clicked
*/
docsApp.directive.backToTop = ['$anchorScroll', function($anchorScroll) {
return function link(scope, element) {
element.on('click', function(event) {
scope.$apply($anchorScroll);
});
};
}];
docsApp.serviceFactory.angularUrls = function($document) {
var urls = {};
@@ -680,7 +695,6 @@ docsApp.controller.DocsController = function($scope, $location, $window, $cookie
var currentPageId = $location.path();
$scope.partialTitle = $scope.currentPage.shortName;
$window._gaq.push(['_trackPageview', currentPageId]);
loadDisqus(currentPageId);
};
/** stores a cookie that is used by apache to decide which manifest ot send */
@@ -892,29 +906,6 @@ docsApp.controller.DocsController = function($scope, $location, $window, $cookie
return namespace;
}
}
function loadDisqus(currentPageId) {
// http://docs.disqus.com/help/2/
window.disqus_shortname = 'angularjs-next';
window.disqus_identifier = currentPageId;
window.disqus_url = 'http://docs.angularjs.org' + currentPageId;
if ($location.host() == 'localhost') {
return; // don't display disqus on localhost, comment this out if needed
//window.disqus_developer = 1;
}
// http://docs.disqus.com/developers/universal/
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://angularjs.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
angular.element(document.getElementById('disqus_thread')).html('');
}
};
+3 -1
View File
@@ -20,6 +20,8 @@ module.exports = function(config) {
junitReporter: {
outputFile: 'test_out/e2e.xml',
suite: 'E2E'
}
},
browserNoActivityTimeout: 90000
});
};
+118 -5
View File
@@ -6,13 +6,22 @@ module.exports = function(config, specificOptions) {
logColors: true,
browsers: ['Chrome'],
browserDisconnectTimeout: 10000,
browserDisconnectTolerance: 2,
browserNoActivityTimeout: 20000,
// config for Travis CI
// SauceLabs config for local development.
sauceLabs: {
testName: specificOptions.testName || 'AngularJS',
startConnect: false,
tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER
startConnect: true
},
// BrowserStack config for local development.
browserStack: {
project: 'AngularJS',
name: specificOptions.testName,
startTunnel: true,
timeout: 600 // 10min
},
// For more browsers on Sauce Labs see:
@@ -49,12 +58,77 @@ module.exports = function(config, specificOptions) {
browserName: 'internet explorer',
platform: 'Windows 2012',
version: '10'
},
'SL_IE_11': {
base: 'SauceLabs',
browserName: 'internet explorer',
platform: 'Windows 8.1',
version: '11'
},
'BS_Chrome': {
base: 'BrowserStack',
browser: 'chrome',
os: 'OS X',
os_version: 'Mountain Lion'
},
'BS_Safari': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mountain Lion'
},
'BS_Firefox': {
base: 'BrowserStack',
browser: 'firefox',
os: 'Windows',
os_version: '8'
},
'BS_IE_8': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '8.0',
os: 'Windows',
os_version: '7'
},
'BS_IE_9': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '9.0',
os: 'Windows',
os_version: '7'
},
'BS_IE_10': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.0',
os: 'Windows',
os_version: '8'
},
'BS_IE_11': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '11.0',
os: 'Windows',
os_version: '8.1'
}
}
});
if (process.env.TRAVIS) {
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
config.logLevel = config.LOG_DEBUG;
config.transports = ['websocket', 'xhr-polling'];
config.browserStack.build = buildLabel;
config.browserStack.startTunnel = false;
config.sauceLabs.build = buildLabel;
config.sauceLabs.startConnect = false;
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
// TODO(vojta): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
config.transports = ['xhr-polling'];
@@ -62,8 +136,47 @@ module.exports = function(config, specificOptions) {
// Debug logging into a file, that we print out at the end of the build.
config.loggers.push({
type: 'file',
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log'),
level: config.LOG_DEBUG
filename: process.env.LOGS_DIR + '/' + (specificOptions.logFile || 'karma.log')
});
}
// Terrible hack to workaround inflexibility of log4js:
// - ignore web-server's 404 warnings,
// - ignore DEBUG logs (on Travis), we log them into a file instead.
var IGNORED_404 = [
'/favicon.ico',
'/%7B%7BtestUrl%7D%7D',
'/someSanitizedUrl',
'/{{testUrl}}'
];
var log4js = require('./node_modules/karma/node_modules/log4js');
var layouts = require('./node_modules/karma/node_modules/log4js/lib/layouts');
var originalConfigure = log4js.configure;
log4js.configure = function(log4jsConfig) {
var consoleAppender = log4jsConfig.appenders.shift();
var originalResult = originalConfigure.call(log4js, log4jsConfig);
var layout = layouts.layout(consoleAppender.layout.type, consoleAppender.layout);
log4js.addAppender(function(log) {
var msg = log.data[0];
// ignore web-server's 404s
if (log.categoryName === 'web-server' && log.level.levelStr === config.LOG_WARN &&
IGNORED_404.some(function(ignoredLog) {return msg.indexOf(ignoredLog) !== -1})) {
return;
}
// on Travis, ignore DEBUG statements
if (process.env.TRAVIS && log.level.levelStr === config.LOG_DEBUG) {
return;
}
console.log(layout(log));
});
return originalResult;
};
};
+46
View File
@@ -0,0 +1,46 @@
var fs = require('fs');
var http = require('http');
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
var HOSTNAME = 'localhost';
var PORTS = require('../grunt/utils').availablePorts;
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
// We need to start fake servers, otherwise the tunnel does not start.
var fakeServers = [];
var hosts = [];
PORTS.forEach(function(port) {
fakeServers.push(http.createServer(function() {}).listen(port));
hosts.push({
name: HOSTNAME,
port: port,
sslFlag: 0
});
});
var tunnel = new BrowserStackTunnel({
key: ACCESS_KEY,
hosts: hosts
});
console.log('Starting tunnel on ports', PORTS.join(', '));
tunnel.start(function(error) {
if (error) {
console.error('Can not establish the tunnel', error);
} else {
console.log('Tunnel established.');
fakeServers.forEach(function(server) {
server.close();
});
if (READY_FILE) {
fs.writeFile(READY_FILE, '');
}
}
});
tunnel.on('error', function(error) {
console.error(error);
});
+1
View File
@@ -0,0 +1 @@
node ./lib/browser-stack/start-tunnel.js &
+19 -1
View File
@@ -5,6 +5,22 @@ var spawn = require('child_process').spawn;
var version;
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
var PORT_MIN = 8000;
var PORT_MAX = 9999;
var TRAVIS_BUILD_NUMBER = parseInt(process.env.TRAVIS_BUILD_NUMBER, 10);
var getRandomPorts = function() {
if (!process.env.TRAVIS) {
return [9876, 9877];
}
// Generate two numbers between PORT_MIN and PORT_MAX, based on TRAVIS_BUILD_NUMBER.
return [
PORT_MIN + (TRAVIS_BUILD_NUMBER % (PORT_MAX - PORT_MIN)),
PORT_MIN + ((TRAVIS_BUILD_NUMBER + 100) % (PORT_MAX - PORT_MIN))
];
};
module.exports = {
init: function() {
@@ -295,5 +311,7 @@ module.exports = {
},
// see http://saucelabs.com/docs/connect#localhost
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876]
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876],
// pseudo-random port numbers for BrowserStack
availablePorts: getRandomPorts()
};
+2 -2
View File
@@ -36,8 +36,8 @@ ARGS=""
if [ ! -z "$TRAVIS_JOB_NUMBER" ]; then
ARGS="$ARGS --tunnel-identifier $TRAVIS_JOB_NUMBER"
fi
if [ ! -z "$SAUCE_CONNECT_READY_FILE" ]; then
ARGS="$ARGS --readyfile $SAUCE_CONNECT_READY_FILE"
if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then
ARGS="$ARGS --readyfile $BROWSER_PROVIDER_READY_FILE"
fi
+8 -6
View File
@@ -1,8 +1,8 @@
{
"name": "angularjs",
"version": "1.2.2",
"cdnVersion": "1.2.1",
"codename": "consciousness-inertia",
"version": "1.2.5",
"cdnVersion": "1.2.4",
"codename": "singularity-expansion",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
@@ -24,7 +24,7 @@
"karma-chrome-launcher": "~0.1.0",
"karma-firefox-launcher": "~0.1.0",
"karma-ng-scenario": "~0.1.0",
"karma-junit-reporter": "git://github.com/karma-runner/karma-junit-reporter#karma-0.11",
"karma-junit-reporter": "~0.2.1",
"karma-sauce-launcher": "~0.1.1",
"karma-script-launcher": "~0.1.0",
"yaml-js": "~0.0.8",
@@ -37,7 +37,9 @@
"promises-aplus-tests": "~1.3.2",
"grunt-shell": "~0.4.0",
"semver": "~2.1.0",
"lodash": "~2.1.0"
"lodash": "~2.1.0",
"karma-browserstack-launcher": "git://github.com/karma-runner/karma-browserstack-launcher.git#master",
"browserstacktunnel-wrapper": "~1.1.1"
},
"licenses": [
{
@@ -46,6 +48,6 @@
}
],
"dependencies": {
"grunt-contrib-jshint": "~0.6.4"
"grunt-contrib-jshint": "~0.7.2"
}
}
+23
View File
@@ -0,0 +1,23 @@
# Angular Bower Script
Script for updating the Angular bower repos from a code.angularjs.org package
Requires `node` (for parsing `bower.json`) and `wget` (for fetching the `angular.zip` from `code.angularjs.org`)
## Instructions
You need to run `./init.sh` the first time you use this script to clone all the repos.
For subsequent updates:
```shell
./publish.sh NEW_VERSION
```
Where `NEW_VERSION` is a version number like `1.2.3`.
## License
MIT
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
#
# init all of the bower repos
#
set -e # fail if any command fails
REPOS=(
angular \
angular-animate \
angular-cookies \
angular-i18n \
angular-loader \
angular-mocks \
angular-route \
angular-resource \
angular-sanitize \
angular-scenario \
angular-touch \
)
cd `dirname $0`
for repo in "${REPOS[@]}"
do
git clone git@github.com:angular/bower-$repo.git
done
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
#
# update all the things
#
set -e # fail if any command fails
cd `dirname $0`
NEW_VERSION=$1
ZIP_FILE=angular-$NEW_VERSION.zip
ZIP_FILE_URL=http://code.angularjs.org/$NEW_VERSION/angular-$NEW_VERSION.zip
ZIP_DIR=angular-$NEW_VERSION
REPOS=(
angular \
angular-animate \
angular-cookies \
angular-i18n \
angular-loader \
angular-mocks \
angular-route \
angular-resource \
angular-sanitize \
angular-scenario \
angular-touch \
)
#
# download and unzip the file
#
if [ ! -f $ZIP_FILE ]; then
wget $ZIP_FILE_URL
unzip $ZIP_FILE
fi
#
# move the files from the zip
#
for repo in "${REPOS[@]}"
do
if [ -f $ZIP_DIR/$repo.js ] # ignore i18l
then
cd bower-$repo
git reset --hard HEAD
git checkout master
git fetch --all
git reset --hard origin/master
cd ..
mv $ZIP_DIR/$repo.* bower-$repo/
fi
done
# move i18n files
mv $ZIP_DIR/i18n/*.js bower-angular-i18n/
# move csp.css
mv $ZIP_DIR/angular-csp.css bower-angular
#
# get the old version number
#
OLD_VERSION=$(node -e "console.log(require('./bower-angular/bower').version)" | sed -e 's/\r//g')
echo $OLD_VERSION
echo $NEW_VERSION
#
# update bower.json
# tag each repo
#
for repo in "${REPOS[@]}"
do
cd bower-$repo
sed -i '' -e "s/$OLD_VERSION/$NEW_VERSION/g" bower.json
git add -A
git commit -m "v$NEW_VERSION"
git tag v$NEW_VERSION
git push origin master
git push origin v$NEW_VERSION
cd ..
done
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
set -e
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
if [ $JOB = "unit" ]; then
grunt ci-checks
grunt test:docgen
grunt test:promises-aplus
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
elif [ $JOB = "e2e" ]; then
grunt test:e2e --browsers SL_Chrome --reporters dots
else
echo "Unknown job type. Please set JOB=unit or JOB=e2e."
fi
@@ -2,6 +2,6 @@
# Wait for Connect to be ready before exiting
while [ ! -f $SAUCE_CONNECT_READY_FILE ]; do
while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do
sleep .5
done
+26 -24
View File
@@ -393,7 +393,7 @@ function valueFn(value) {return function() {return value;};}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is undefined.
*/
function isUndefined(value){return typeof value == 'undefined';}
function isUndefined(value){return typeof value === 'undefined';}
/**
@@ -407,7 +407,7 @@ function isUndefined(value){return typeof value == 'undefined';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is defined.
*/
function isDefined(value){return typeof value != 'undefined';}
function isDefined(value){return typeof value !== 'undefined';}
/**
@@ -422,7 +422,7 @@ function isDefined(value){return typeof value != 'undefined';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Object` but not `null`.
*/
function isObject(value){return value != null && typeof value == 'object';}
function isObject(value){return value != null && typeof value === 'object';}
/**
@@ -436,7 +436,7 @@ function isObject(value){return value != null && typeof value == 'object';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `String`.
*/
function isString(value){return typeof value == 'string';}
function isString(value){return typeof value === 'string';}
/**
@@ -450,7 +450,7 @@ function isString(value){return typeof value == 'string';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
function isNumber(value){return typeof value == 'number';}
function isNumber(value){return typeof value === 'number';}
/**
@@ -465,7 +465,7 @@ function isNumber(value){return typeof value == 'number';}
* @returns {boolean} True if `value` is a `Date`.
*/
function isDate(value){
return toString.apply(value) == '[object Date]';
return toString.call(value) === '[object Date]';
}
@@ -481,7 +481,7 @@ function isDate(value){
* @returns {boolean} True if `value` is an `Array`.
*/
function isArray(value) {
return toString.apply(value) == '[object Array]';
return toString.call(value) === '[object Array]';
}
@@ -496,7 +496,7 @@ function isArray(value) {
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Function`.
*/
function isFunction(value){return typeof value == 'function';}
function isFunction(value){return typeof value === 'function';}
/**
@@ -507,7 +507,7 @@ function isFunction(value){return typeof value == 'function';}
* @returns {boolean} True if `value` is a `RegExp`.
*/
function isRegExp(value) {
return toString.apply(value) == '[object RegExp]';
return toString.call(value) === '[object RegExp]';
}
@@ -529,12 +529,12 @@ function isScope(obj) {
function isFile(obj) {
return toString.apply(obj) === '[object File]';
return toString.call(obj) === '[object File]';
}
function isBoolean(value) {
return typeof value == 'boolean';
return typeof value === 'boolean';
}
@@ -544,7 +544,7 @@ var trim = (function() {
// TODO: we should move this into IE/ES5 polyfill
if (!String.prototype.trim) {
return function(value) {
return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value;
return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value;
};
}
return function(value) {
@@ -565,9 +565,9 @@ var trim = (function() {
* @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
*/
function isElement(node) {
return node &&
return !!(node &&
(node.nodeName // we are a direct element
|| (node.on && node.find)); // we have an on and find method part of jQuery API
|| (node.on && node.find))); // we have an on and find method part of jQuery API
}
/**
@@ -638,7 +638,7 @@ function includes(array, obj) {
function indexOf(array, obj) {
if (array.indexOf) return array.indexOf(obj);
for ( var i = 0; i < array.length; i++) {
for (var i = 0; i < array.length; i++) {
if (obj === array[i]) return i;
}
return -1;
@@ -768,7 +768,7 @@ function shallowCopy(src, dst) {
for(var key in src) {
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
// so we don't need to worry hasOwnProperty here
// so we don't need to worry about using our custom hasOwnProperty here
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
dst[key] = src[key];
}
@@ -974,7 +974,7 @@ function startingTag(element) {
try {
// turns out IE does not let you set .html() on elements which
// are not allowed to have children. So we just ignore it.
element.html('');
element.empty();
} catch(e) {}
// As Per DOM Standards
var TEXT_NODE = 3;
@@ -1330,23 +1330,25 @@ function getter(obj, path, bindFnToScope) {
}
/**
* Return the siblings between `startNode` and `endNode`, inclusive
* @param {Object} object with `startNode` and `endNode` properties
* Return the DOM siblings between the first and last node in the given array.
* @param {Array} array like object
* @returns jQlite object containing the elements
*/
function getBlockElements(block) {
if (block.startNode === block.endNode) {
return jqLite(block.startNode);
function getBlockElements(nodes) {
var startNode = nodes[0],
endNode = nodes[nodes.length - 1];
if (startNode === endNode) {
return jqLite(startNode);
}
var element = block.startNode;
var element = startNode;
var elements = [element];
do {
element = element.nextSibling;
if (!element) break;
elements.push(element);
} while (element !== block.endNode);
} while (element !== endNode);
return jqLite(elements);
}
+9
View File
@@ -28,6 +28,7 @@
ngHideDirective,
ngIfDirective,
ngIncludeDirective,
ngIncludeFillContentDirective,
ngInitDirective,
ngNonBindableDirective,
ngPluralizeDirective,
@@ -65,6 +66,7 @@
$ParseProvider,
$RootScopeProvider,
$QProvider,
$$SanitizeUriProvider,
$SceProvider,
$SceDelegateProvider,
$SnifferProvider,
@@ -136,6 +138,10 @@ function publishExternalAPI(angular){
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
// $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
$provide.provider({
$$sanitizeUri: $$SanitizeUriProvider
});
$provide.provider('$compile', $CompileProvider).
directive({
a: htmlAnchorDirective,
@@ -176,6 +182,9 @@ function publishExternalAPI(angular){
ngRequired: requiredDirective,
ngValue: ngValueDirective
}).
directive({
ngInclude: ngIncludeFillContentDirective
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
$provide.provider({
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {
+26 -19
View File
@@ -27,6 +27,28 @@
* $rootScope.$digest();
* });
* </pre>
*
* Sometimes you want to get access to the injector of a currently running Angular app
* from outside Angular. Perhaps, you want to inject and compile some markup after the
* application has been bootstrapped. You can do this using extra `injector()` added
* to JQuery/jqLite elements. See {@link angular.element}.
*
* *This is fairly rare but could be the case if a third party library is injecting the
* markup.*
*
* In the following example a new block of HTML containing a `ng-controller`
* directive is added to the end of the document body by JQuery. We then compile and link
* it into the current AngularJS scope.
*
* <pre>
* var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
* $(document.body).append($div);
*
* angular.element(document).injector().invoke(function($compile) {
* var scope = angular.element($div).scope();
* $compile($div)(scope);
* });
* </pre>
*/
@@ -221,7 +243,7 @@ function annotate(fn) {
* // ...
* }
* // Define function dependencies
* MyController.$inject = ['$scope', '$route'];
* MyController['$inject'] = ['$scope', '$route'];
*
* // Then
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
@@ -747,24 +769,9 @@ function createInjector(modulesToLoad) {
fn = fn[length];
}
// Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
switch (self ? -1 : args.length) {
case 0: return fn();
case 1: return fn(args[0]);
case 2: return fn(args[0], args[1]);
case 3: return fn(args[0], args[1], args[2]);
case 4: return fn(args[0], args[1], args[2], args[3]);
case 5: return fn(args[0], args[1], args[2], args[3], args[4]);
case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
args[8]);
case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7],
args[8], args[9]);
default: return fn.apply(self, args);
}
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
}
function instantiate(Type, locals) {
+23 -5
View File
@@ -46,6 +46,7 @@
* - [`contents()`](http://api.jquery.com/contents/)
* - [`css()`](http://api.jquery.com/css/)
* - [`data()`](http://api.jquery.com/data/)
* - [`empty()`](http://api.jquery.com/empty/)
* - [`eq()`](http://api.jquery.com/eq/)
* - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
* - [`hasClass()`](http://api.jquery.com/hasClass/)
@@ -358,6 +359,15 @@ function jqLiteInheritedData(element, name, value) {
}
}
function jqLiteEmpty(element) {
for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
jqLiteDealoc(childNodes[i]);
}
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
@@ -552,7 +562,9 @@ forEach({
jqLiteDealoc(childNodes[i]);
}
element.innerHTML = value;
}
},
empty: jqLiteEmpty
}, function(fn, name){
/**
* Properties: writes return selection, reads return first value
@@ -562,11 +574,13 @@ forEach({
// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
if (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined) {
// jqLiteEmpty takes no arguments but is a setter.
if (fn !== jqLiteEmpty &&
(((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) {
if (isObject(arg1)) {
// we are a write, but the object properties are the key/values
for(i=0; i < this.length; i++) {
for (i = 0; i < this.length; i++) {
if (fn === jqLiteData) {
// data() takes the whole object in jQuery
fn(this[i], arg1);
@@ -591,7 +605,7 @@ forEach({
}
} else {
// we are a write, so apply to all children
for(i=0; i < this.length; i++) {
for (i = 0; i < this.length; i++) {
fn(this[i], arg1, arg2);
}
// return self for chaining
@@ -822,7 +836,11 @@ forEach({
},
find: function(element, selector) {
return element.getElementsByTagName(selector);
if (element.getElementsByTagName) {
return element.getElementsByTagName(selector);
} else {
return [];
}
},
clone: jqLiteClone,
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
'use strict';
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
+29 -35
View File
@@ -192,7 +192,7 @@
* * (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's parents. Throw an error if not found.
* * `?^` - Attempt to locate the required controller by searching the element's parentsor pass `null` to the
* * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the
* `link` fn if not found.
*
*
@@ -283,7 +283,7 @@
* </div>
*
* <div class="alert alert-error">
* **Note:** The `transclude` function that is passed to the compile function is deperecated, as it
* **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
* e.g. does not know about the right outer scope. Please use the transclude function that is passed
* to the link function instead.
* </div>
@@ -493,14 +493,12 @@ var $compileMinErr = minErr('$compile');
*
* @description
*/
$CompileProvider.$inject = ['$provide'];
function $CompileProvider($provide) {
$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -584,10 +582,11 @@ function $CompileProvider($provide) {
*/
this.aHrefSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
aHrefSanitizationWhitelist = regexp;
$$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp);
return this;
} else {
return $$sanitizeUriProvider.aHrefSanitizationWhitelist();
}
return aHrefSanitizationWhitelist;
};
@@ -614,18 +613,18 @@ function $CompileProvider($provide) {
*/
this.imgSrcSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
imgSrcSanitizationWhitelist = regexp;
$$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp);
return this;
} else {
return $$sanitizeUriProvider.imgSrcSanitizationWhitelist();
}
return imgSrcSanitizationWhitelist;
};
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
'$controller', '$rootScope', '$document', '$sce', '$animate',
'$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
$controller, $rootScope, $document, $sce, $animate) {
$controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) {
var Attributes = function(element, attr) {
this.$$element = element;
@@ -730,16 +729,7 @@ function $CompileProvider($provide) {
// sanitize a[href] and img[src] values
if ((nodeName === 'A' && key === 'href') ||
(nodeName === 'IMG' && key === 'src')) {
// NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case.
if (!msie || msie >= 8 ) {
normalizedVal = urlResolve(value).href;
if (normalizedVal !== '') {
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
this[key] = value = 'unsafe:' + normalizedVal;
}
}
}
this[key] = value = $$sanitizeUri(value, key === 'src');
}
if (writeAttr !== false) {
@@ -941,7 +931,7 @@ function $CompileProvider($provide) {
createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn)
);
} else {
nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
}
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
@@ -1229,7 +1219,7 @@ function $CompileProvider($provide) {
});
} else {
$template = jqLite(jqLiteClone(compileNode)).contents();
$compileNode.html(''); // clear contents
$compileNode.empty(); // clear contents
childTranscludeFn = compile($template, transcludeFn);
}
}
@@ -1410,7 +1400,7 @@ function $CompileProvider($provide) {
optional = (match[2] == '?'),
mode = match[1], // @, =, or &
lastValue,
parentGet, parentSet;
parentGet, parentSet, compare;
isolateScope.$$isolateBindings[scopeName] = mode + attrName;
@@ -1433,6 +1423,11 @@ function $CompileProvider($provide) {
return;
}
parentGet = $parse(attrs[attrName]);
if (parentGet.literal) {
compare = equals;
} else {
compare = function(a,b) { return a === b; };
}
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
lastValue = isolateScope[scopeName] = parentGet(scope);
@@ -1443,19 +1438,18 @@ function $CompileProvider($provide) {
lastValue = isolateScope[scopeName] = parentGet(scope);
isolateScope.$watch(function parentValueWatch() {
var parentValue = parentGet(scope);
if (parentValue !== isolateScope[scopeName]) {
if (!compare(parentValue, isolateScope[scopeName])) {
// we are out of sync and need to copy
if (parentValue !== lastValue) {
if (!compare(parentValue, lastValue)) {
// parent changed and it has precedence
lastValue = isolateScope[scopeName] = parentValue;
isolateScope[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = lastValue = isolateScope[scopeName]);
parentSet(scope, parentValue = isolateScope[scopeName]);
}
}
return parentValue;
});
return lastValue = parentValue;
}, null, parentGet.literal);
break;
case '&':
@@ -1657,7 +1651,7 @@ function $CompileProvider($provide) {
? origAsyncDirective.templateUrl($compileNode, tAttrs)
: origAsyncDirective.templateUrl;
$compileNode.html('');
$compileNode.empty();
$http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}).
success(function(content) {
+10 -8
View File
@@ -32,13 +32,15 @@ var htmlAnchorDirective = valueFn({
element.append(document.createComment('IE fix'));
}
return function(scope, element) {
element.on('click', function(event){
// if we have no href url, then don't navigate anywhere.
if (!element.attr('href')) {
event.preventDefault();
}
});
};
if (!attr.href && !attr.name) {
return function(scope, element) {
element.on('click', function(event){
// if we have no href url, then don't navigate anywhere.
if (!element.attr('href')) {
event.preventDefault();
}
});
};
}
}
});
+15 -2
View File
@@ -21,9 +21,22 @@ var nullFormCtrl = {
* @property {Object} $error Is an object hash, containing references to all invalid controls or
* forms, where:
*
* - keys are validation tokens (error names) — such as `required`, `url` or `email`,
* - values are arrays of controls or forms that are invalid with given error.
* - keys are validation tokens (error names),
* - values are arrays of controls or forms that are invalid for given error name.
*
*
* Built-in validation tokens:
*
* - `email`
* - `max`
* - `maxlength`
* - `min`
* - `minlength`
* - `number`
* - `pattern`
* - `required`
* - `url`
*
* @description
* `FormController` keeps track of all its controls and nested forms as well as state of them,
* such as being valid/invalid or dirty/pristine.
+6 -38
View File
@@ -449,15 +449,15 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
deferListener();
});
// if user paste into input using mouse, we need "change" event to catch it
element.on('change', listener);
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
if ($sniffer.hasEvent('paste')) {
element.on('paste cut', deferListener);
}
}
// if user paste into input using mouse on older browser
// or form autocomplete on newer browser, we need "change" event to catch it
element.on('change', listener);
ctrl.$render = function() {
element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
@@ -951,39 +951,6 @@ var VALID_CLASS = 'ng-valid',
</file>
* </example>
*
* ## Isolated Scope Pitfall
*
* Note that if you have a directive with an isolated scope, you cannot require `ngModel`
* since the model value will be looked up on the isolated scope rather than the outer scope.
* When the directive updates the model value, calling `ngModel.$setViewValue()` the property
* on the outer scope will not be updated. However you can get around this by using $parent.
*
* Here is an example of this situation. You'll notice that the first div is not updating the input.
* However the second div can update the input properly.
*
* <example module="badIsolatedDirective">
<file name="script.js">
angular.module('badIsolatedDirective', []).directive('isolate', function() {
return {
require: 'ngModel',
scope: { },
template: '<input ng-model="innerModel">',
link: function(scope, element, attrs, ngModel) {
scope.$watch('innerModel', function(value) {
console.log(value);
ngModel.$setViewValue(value);
});
}
};
});
</file>
<file name="index.html">
<input ng-model="someModel"/>
<div isolate ng-model="someModel"></div>
<div isolate ng-model="$parent.someModel"></div>
</file>
* </example>
*
*
*/
var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse',
@@ -1130,7 +1097,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* It will update the $viewValue, then pass this value through each of the functions in `$parsers`,
* which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to
* `$modelValue` and the **expression** specified in the `ng-model` attribute.
*
*
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
*
* Note that calling this function does not trigger a `$digest`.
@@ -1187,6 +1154,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ctrl.$render();
}
}
return value;
});
}];
@@ -1463,7 +1432,6 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
id="{{name}}"
name="favorite">
</label>
</span>
<div>You chose {{my.favorite}}</div>
</form>
</doc:source>
+6 -3
View File
@@ -94,9 +94,12 @@ var ngIfDirective = ['$animate', function($animate) {
if (!childScope) {
childScope = $scope.$new();
$transclude(childScope, function (clone) {
clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when it's template arrives.
block = {
startNode: clone[0],
endNode: clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ')
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
@@ -109,7 +112,7 @@ var ngIfDirective = ['$animate', function($animate) {
}
if (block) {
$animate.leave(getBlockElements(block));
$animate.leave(getBlockElements(block.clone));
block = null;
}
}
+27 -7
View File
@@ -147,13 +147,14 @@
* @description
* Emitted every time the ngInclude content is reloaded.
*/
var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animate', '$sce',
function($http, $templateCache, $anchorScroll, $compile, $animate, $sce) {
var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce',
function($http, $templateCache, $anchorScroll, $animate, $sce) {
return {
restrict: 'ECA',
priority: 400,
terminal: true,
transclude: 'element',
controller: angular.noop,
compile: function(element, attr) {
var srcExp = attr.ngInclude || attr.src,
onloadExp = attr.onload || '',
@@ -187,6 +188,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
$http.get(src, {cache: $templateCache}).success(function(response) {
if (thisChangeId !== changeCounter) return;
var newScope = scope.$new();
ctrl.template = response;
// Note: This will also link all children of ng-include that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
@@ -194,15 +196,14 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, noop);
cleanupLastIncludeContent();
var clone = $transclude(newScope, function(clone) {
cleanupLastIncludeContent();
$animate.enter(clone, null, $element, afterAnimation);
});
currentScope = newScope;
currentElement = clone;
currentElement.html(response);
$animate.enter(currentElement, null, $element, afterAnimation);
$compile(currentElement.contents())(currentScope);
currentScope.$emit('$includeContentLoaded');
scope.$eval(onloadExp);
}).error(function() {
@@ -211,9 +212,28 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile'
scope.$emit('$includeContentRequested');
} else {
cleanupLastIncludeContent();
ctrl.template = null;
}
});
};
}
};
}];
// This directive is called during the $transclude call of the first `ngInclude` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngInclude
// is called.
var ngIncludeFillContentDirective = ['$compile',
function($compile) {
return {
restrict: 'ECA',
priority: -400,
require: 'ngInclude',
link: function(scope, $element, $attr, ctrl) {
$element.html(ctrl.template);
$compile($element.contents())(scope);
}
};
}];
+3
View File
@@ -16,6 +16,8 @@
* to initialize values on a scope.
* </div>
*
* @priority 450
*
* @element ANY
* @param {expression} ngInit {@link guide/expression Expression} to eval.
*
@@ -47,6 +49,7 @@
</doc:example>
*/
var ngInitDirective = ngDirective({
priority: 450,
compile: function() {
return {
pre: function(scope, element, attrs) {
+20 -10
View File
@@ -301,7 +301,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
} else if (nextBlockMap.hasOwnProperty(trackById)) {
// restore lastBlockMap
forEach(nextBlockOrder, function(block) {
if (block && block.startNode) lastBlockMap[block.id] = block;
if (block && block.scope) lastBlockMap[block.id] = block;
});
// This is a duplicate and we need to throw an error
throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}",
@@ -318,7 +318,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
// lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn
if (lastBlockMap.hasOwnProperty(key)) {
block = lastBlockMap[key];
elementsToRemove = getBlockElements(block);
elementsToRemove = getBlockElements(block.clone);
$animate.leave(elementsToRemove);
forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; });
block.scope.$destroy();
@@ -330,9 +330,9 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
key = (collection === collectionKeys) ? index : collectionKeys[index];
value = collection[key];
block = nextBlockOrder[index];
if (nextBlockOrder[index - 1]) previousNode = nextBlockOrder[index - 1].endNode;
if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]);
if (block.startNode) {
if (block.scope) {
// if we have already seen this object, then we need to reuse the
// associated scope/element
childScope = block.scope;
@@ -342,11 +342,11 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
nextNode = nextNode.nextSibling;
} while(nextNode && nextNode[NG_REMOVED]);
if (block.startNode != nextNode) {
if (getBlockStart(block) != nextNode) {
// existing item which got moved
$animate.move(getBlockElements(block), null, jqLite(previousNode));
$animate.move(getBlockElements(block.clone), null, jqLite(previousNode));
}
previousNode = block.endNode;
previousNode = getBlockEnd(block);
} else {
// new item which we don't know about
childScope = $scope.$new();
@@ -362,14 +362,16 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
childScope.$odd = !(childScope.$even = (index&1) === 0);
// jshint bitwise: true
if (!block.startNode) {
if (!block.scope) {
$transclude(childScope, function(clone) {
clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' ');
$animate.enter(clone, null, jqLite(previousNode));
previousNode = clone;
block.scope = childScope;
block.startNode = previousNode && previousNode.endNode ? previousNode.endNode : clone[0];
block.endNode = clone[clone.length - 1];
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when it's template arrives.
block.clone = clone;
nextBlockMap[block.id] = block;
});
}
@@ -378,5 +380,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
});
}
};
function getBlockStart(block) {
return block.clone[0];
}
function getBlockEnd(block) {
return block.clone[block.clone.length - 1];
}
}];
+13 -5
View File
@@ -6,19 +6,26 @@
* @restrict EA
*
* @description
* The ngSwitch directive is used to conditionally swap DOM structure on your template based on a scope expression.
* Elements within ngSwitch but without ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
* The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
* Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
* as specified in the template.
*
* The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
* from the template cache), ngSwitch simply choses one of the nested elements and makes it visible based on which element
* from the template cache), `ngSwitch` simply choses one of the nested elements and makes it visible based on which element
* matches the value obtained from the evaluated expression. In other words, you define a container element
* (where you place the directive), place an expression on the **on="..." attribute**
* (or the **ng-switch="..." attribute**), define any inner elements inside of the directive and place
* (where you place the directive), place an expression on the **`on="..."` attribute**
* (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
* a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
* expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
* attribute is displayed.
*
* <div class="alert alert-info">
* Be aware that the attribute values to match against cannot be expressions. They are interpreted
* as literal string values to match against.
* For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
* value of the expression `$scope.someVal`.
* </div>
* @animations
* enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
* leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
@@ -30,6 +37,7 @@
* <ANY ng-switch-default>...</ANY>
* </ANY>
*
*
* @scope
* @priority 800
* @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
+1 -1
View File
@@ -69,7 +69,7 @@ var ngTranscludeDirective = ngDirective({
link: function($scope, $element, $attrs, controller) {
controller.$transclude(function(clone) {
$element.html('');
$element.empty();
$element.append(clone);
});
}
+2 -2
View File
@@ -333,13 +333,13 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// becomes the compilation root
nullOption.removeClass('ng-scope');
// we need to remove it before calling selectElement.html('') because otherwise IE will
// we need to remove it before calling selectElement.empty() because otherwise IE will
// remove the label from the element. wtf?
nullOption.remove();
}
// clear contents, we'll add what's needed based on the model
selectElement.html('');
selectElement.empty();
selectElement.on('change', function() {
scope.$apply(function() {
+4 -5
View File
@@ -28,12 +28,11 @@ var XHR = window.XMLHttpRequest || function() {
*/
function $HttpBackendProvider() {
this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks,
$document[0], $window.location.protocol.replace(':', ''));
return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, $document[0]);
}];
}
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument) {
var ABORTED = -1;
// TODO(vojta): fix the signature
@@ -113,14 +112,14 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
}
function completeRequest(callback, status, response, headersString) {
var protocol = locationProtocol || urlResolve(url).protocol;
var protocol = urlResolve(url).protocol;
// cancel timeout and subsequent timeout promise resolution
timeoutId && $browserDefer.cancel(timeoutId);
jsonpDone = xhr = null;
// fix status code for file protocol (it's always 0)
status = (protocol == 'file') ? (response ? 200 : 404) : status;
status = (protocol == 'file' && status === 0) ? (response ? 200 : 404) : status;
// normalize IE bug (http://bugs.jquery.com/ticket/1450)
status = status == 1223 ? 204 : status;
+2 -2
View File
@@ -103,8 +103,8 @@ function $InterpolateProvider() {
*
<pre>
var $interpolate = ...; // injected
var exp = $interpolate('Hello {{name}}!');
expect(exp({name:'Angular'}).toEqual('Hello Angular!');
var exp = $interpolate('Hello {{name | uppercase}}!');
expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
</pre>
*
*
+40
View File
@@ -179,7 +179,47 @@ function LocationHashbangUrl(appBase, hashPrefix) {
hashPrefix);
}
parseAppUrl(withoutHashUrl, this, appBase);
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
this.$$compose();
/*
* In Windows, on an anchor node on documents loaded from
* the filesystem, the browser will return a pathname
* prefixed with the drive name ('/C:/path') when a
* pathname without a drive is set:
* * a.setAttribute('href', '/foo')
* * a.pathname === '/C:/foo' //true
*
* Inside of Angular, we're always using pathnames that
* do not include drive names for routing.
*/
function removeWindowsDriveName (path, url, base) {
/*
Matches paths for file protocol on windows,
such as /C:/foo/bar, and captures only /foo/bar.
*/
var windowsFilePathExp = /^\/?.*?:(\/.*)/;
var firstPathSegmentMatch;
//Get the relative path from the input URL.
if (url.indexOf(base) === 0) {
url = url.replace(base, '');
}
/*
* The input URL intentionally contains a
* first path segment that ends with a colon.
*/
if (windowsFilePathExp.exec(url)) {
return path;
}
firstPathSegmentMatch = windowsFilePathExp.exec(path);
return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
}
};
/**
+17 -16
View File
@@ -44,23 +44,24 @@ function ensureSafeMemberName(name, fullExpression) {
function ensureSafeObject(obj, fullExpression) {
// nifty check if obj is Function that is fast and works across iframes and other contexts
if (obj && obj.constructor === obj) {
throw $parseMinErr('isecfn',
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isWindow(obj)
obj && obj.document && obj.location && obj.alert && obj.setInterval) {
throw $parseMinErr('isecwindow',
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isElement(obj)
obj && (obj.nodeName || (obj.on && obj.find))) {
throw $parseMinErr('isecdom',
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else {
return obj;
if (obj) {
if (obj.constructor === obj) {
throw $parseMinErr('isecfn',
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isWindow(obj)
obj.document && obj.location && obj.alert && obj.setInterval) {
throw $parseMinErr('isecwindow',
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isElement(obj)
obj.children && (obj.nodeName || (obj.on && obj.find))) {
throw $parseMinErr('isecdom',
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
fullExpression);
}
}
return obj;
}
var OPERATORS = {
+1 -1
View File
@@ -136,7 +136,7 @@
*
* # Differences between Kris Kowal's Q and $q
*
* There are three main differences:
* There are two main differences:
*
* - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
* mechanism in angular, which means faster propagation of resolution or rejection into your
+79 -57
View File
@@ -71,6 +71,7 @@
function $RootScopeProvider(){
var TTL = 10;
var $rootScopeMinErr = minErr('$rootScope');
var lastDirtyWatch = null;
this.digestTtl = function(value) {
if (arguments.length) {
@@ -155,11 +156,11 @@ function $RootScopeProvider(){
* @description
* Creates a new child {@link ng.$rootScope.Scope scope}.
*
* The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
* {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the
* scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
* The parent scope will propagate the {@link ng.$rootScope.Scope#methods_$digest $digest()} and
* {@link ng.$rootScope.Scope#methods_$digest $digest()} events. The scope can be removed from the
* scope hierarchy using {@link ng.$rootScope.Scope#methods_$destroy $destroy()}.
*
* {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is
* {@link ng.$rootScope.Scope#methods_$destroy $destroy()} must be called on a scope when it is
* desired for the scope and its child scopes to be permanently detached from the parent and
* thus stop participating in model change detection and listener notification by invoking.
*
@@ -172,7 +173,7 @@ function $RootScopeProvider(){
*
*/
$new: function(isolate) {
var Child,
var ChildScope,
child;
if (isolate) {
@@ -182,11 +183,11 @@ function $RootScopeProvider(){
child.$$asyncQueue = this.$$asyncQueue;
child.$$postDigestQueue = this.$$postDigestQueue;
} else {
Child = function() {}; // should be anonymous; This is so that when the minifier munges
ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges
// the name it does not become random set of chars. This will then show up as class
// name in the debugger.
Child.prototype = this;
child = new Child();
ChildScope.prototype = this;
child = new ChildScope();
child.$id = nextUid();
}
child['this'] = child;
@@ -212,11 +213,11 @@ function $RootScopeProvider(){
* @description
* Registers a `listener` callback to be executed whenever the `watchExpression` changes.
*
* - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest
* - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#methods_$digest
* $digest()} and should return the value that will be watched. (Since
* {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the
* {@link ng.$rootScope.Scope#methods_$digest $digest()} reruns when it detects changes the
* `watchExpression` can execute multiple times per
* {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
* {@link ng.$rootScope.Scope#methods_$digest $digest()} and should be idempotent.)
* - The `listener` is called only when the value from the current `watchExpression` and the
* previous call to `watchExpression` are not equal (with the exception of the initial run,
* see below). The inequality is determined according to
@@ -228,13 +229,13 @@ function $RootScopeProvider(){
* iteration limit is 10 to prevent an infinite loop deadlock.
*
*
* If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
* If you want to be notified whenever {@link ng.$rootScope.Scope#methods_$digest $digest} is called,
* you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
* can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a
* can execute multiple times per {@link ng.$rootScope.Scope#methods_$digest $digest} cycle when a
* change is detected, be prepared for multiple calls to your listener.)
*
* After a watcher is registered with the scope, the `listener` fn is called asynchronously
* (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
* (via {@link ng.$rootScope.Scope#methods_$evalAsync $evalAsync}) to initialize the
* watcher. In rare cases, this is undesirable because the listener is called when the result
* of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
* can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
@@ -266,7 +267,7 @@ function $RootScopeProvider(){
// Using a listener function
// Using a listener function
var food;
scope.foodCounter = 0;
expect(scope.foodCounter).toEqual(0);
@@ -291,14 +292,14 @@ function $RootScopeProvider(){
// Update food and run digest. Now the counter will increment
food = 'cheeseburger';
scope.$digest();
expect(scope.foodCounter).toEqual(1);
expect(scope.foodCounter).toEqual(1);
* </pre>
*
*
*
* @param {(function()|string)} watchExpression Expression that is evaluated on each
* {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers
* {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. A change in the return value triggers
* a call to the `listener`.
*
* - `string`: Evaluated as {@link guide/expression expression}
@@ -325,6 +326,8 @@ function $RootScopeProvider(){
eq: !!objectEquality
};
lastDirtyWatch = null;
// in the case user pass string, we need to compile it, do we really need this ?
if (!isFunction(listener)) {
var listenFn = compileToFn(listener || noop, 'listener');
@@ -394,7 +397,7 @@ function $RootScopeProvider(){
*
* @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The
* expression value should evaluate to an object or an array which is observed on each
* {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
* {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. Any shallow change within the
* collection will trigger a call to the `listener`.
*
* @param {function(newCollection, oldCollection, scope)} listener a callback function that is
@@ -499,9 +502,9 @@ function $RootScopeProvider(){
* @function
*
* @description
* Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and
* its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change
* the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers}
* Processes all of the {@link ng.$rootScope.Scope#methods_$watch watchers} of the current scope and
* its children. Because a {@link ng.$rootScope.Scope#methods_$watch watcher}'s listener can change
* the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#methods_$watch watchers}
* until no more listeners are firing. This means that it is possible to get into an infinite
* loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of
* iterations exceeds 10.
@@ -509,12 +512,12 @@ function $RootScopeProvider(){
* Usually, you don't call `$digest()` directly in
* {@link ng.directive:ngController controllers} or in
* {@link ng.$compileProvider#methods_directive directives}.
* Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within
* Instead, you should call {@link ng.$rootScope.Scope#methods_$apply $apply()} (typically from within
* a {@link ng.$compileProvider#methods_directive directives}), which will force a `$digest()`.
*
* If you want to be notified whenever `$digest()` is called,
* you can register a `watchExpression` function with
* {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`.
* {@link ng.$rootScope.Scope#methods_$watch $watch()} with no `listener`.
*
* In unit tests, you may need to call `$digest()` to simulate the scope life cycle.
*
@@ -553,6 +556,8 @@ function $RootScopeProvider(){
beginPhase('$digest');
lastDirtyWatch = null;
do { // "while dirty" loop
dirty = false;
current = target;
@@ -562,10 +567,13 @@ function $RootScopeProvider(){
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
lastDirtyWatch = null;
}
traverseScopesLoop:
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
// process our watches
@@ -575,25 +583,34 @@ function $RootScopeProvider(){
watch = watchers[length];
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if (watch && (value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value == 'number' && typeof last == 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
watch.last = watch.eq ? copy(value) : value;
watch.fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
logMsg = (isFunction(watch.exp))
? 'fn: ' + (watch.exp.name || watch.exp.toString())
: watch.exp;
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
watchLog[logIdx].push(logMsg);
if (watch) {
if ((value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (typeof value == 'number' && typeof last == 'number'
&& isNaN(value) && isNaN(last)))) {
dirty = true;
lastDirtyWatch = watch;
watch.last = watch.eq ? copy(value) : value;
watch.fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
logMsg = (isFunction(watch.exp))
? 'fn: ' + (watch.exp.name || watch.exp.toString())
: watch.exp;
logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
watchLog[logIdx].push(logMsg);
}
} else if (watch === lastDirtyWatch) {
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
// have already been tested.
dirty = false;
break traverseScopesLoop;
}
}
} catch (e) {
clearPhase();
$exceptionHandler(e);
}
}
@@ -602,13 +619,16 @@ function $RootScopeProvider(){
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $broadcast
if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
if (!(next = (current.$$childHead ||
(current !== target && current.$$nextSibling)))) {
while(current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));
// `break traverseScopesLoop;` takes us to here
if(dirty && !(ttl--)) {
clearPhase();
throw $rootScopeMinErr('infdig',
@@ -616,6 +636,7 @@ function $RootScopeProvider(){
'Watchers fired in the last 5 iterations: {1}',
TTL, toJson(watchLog));
}
} while (dirty || asyncQueue.length);
clearPhase();
@@ -651,7 +672,7 @@ function $RootScopeProvider(){
*
* @description
* Removes the current scope (and all of its children) from the parent scope. Removal implies
* that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
* that calls to {@link ng.$rootScope.Scope#methods_$digest $digest()} will no longer
* propagate to the current scope and its children. Removal also implies that the current
* scope is eligible for garbage collection.
*
@@ -668,11 +689,12 @@ function $RootScopeProvider(){
*/
$destroy: function() {
// we can't destroy the root scope or a scope that has been already destroyed
if ($rootScope == this || this.$$destroyed) return;
if (this.$$destroyed) return;
var parent = this.$parent;
this.$broadcast('$destroy');
this.$$destroyed = true;
if (this === $rootScope) return;
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
@@ -710,7 +732,7 @@ function $RootScopeProvider(){
*
* - `string`: execute using the rules as defined in {@link guide/expression expression}.
* - `function(scope)`: execute the function with the current `scope` parameter.
*
*
* @param {(object)=} locals Local variables object, useful for overriding values in scope.
* @returns {*} The result of evaluating the expression.
*/
@@ -732,7 +754,7 @@ function $RootScopeProvider(){
*
* - it will execute after the function that scheduled the evaluation (preferably before DOM
* rendering).
* - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
* - at least one {@link ng.$rootScope.Scope#methods_$digest $digest cycle} will be performed after
* `expression` execution.
*
* Any exceptions from the execution of the expression are forwarded to the
@@ -777,7 +799,7 @@ function $RootScopeProvider(){
* framework. (For example from browser DOM events, setTimeout, XHR or third party libraries).
* Because we are calling into the angular framework we need to perform proper scope life
* cycle of {@link ng.$exceptionHandler exception handling},
* {@link ng.$rootScope.Scope#$digest executing watches}.
* {@link ng.$rootScope.Scope#methods_$digest executing watches}.
*
* ## Life cycle
*
@@ -798,11 +820,11 @@ function $RootScopeProvider(){
* Scope's `$apply()` method transitions through the following stages:
*
* 1. The {@link guide/expression expression} is executed using the
* {@link ng.$rootScope.Scope#$eval $eval()} method.
* {@link ng.$rootScope.Scope#methods_$eval $eval()} method.
* 2. Any exceptions from the execution of the expression are forwarded to the
* {@link ng.$exceptionHandler $exceptionHandler} service.
* 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the
* expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
* 3. The {@link ng.$rootScope.Scope#methods_$watch watch} listeners are fired immediately after the
* expression was executed using the {@link ng.$rootScope.Scope#methods_$digest $digest()} method.
*
*
* @param {(string|function())=} exp An angular expression to be executed.
@@ -836,7 +858,7 @@ function $RootScopeProvider(){
* @function
*
* @description
* Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for
* Listens on events of a given type. See {@link ng.$rootScope.Scope#methods_$emit $emit} for
* discussion of event life cycle.
*
* The event listener function format is: `function(event, args...)`. The `event` object
@@ -877,20 +899,20 @@ function $RootScopeProvider(){
*
* @description
* Dispatches an event `name` upwards through the scope hierarchy notifying the
* registered {@link ng.$rootScope.Scope#$on} listeners.
* registered {@link ng.$rootScope.Scope#methods_$on} listeners.
*
* The event life cycle starts at the scope on which `$emit` was called. All
* {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
* {@link ng.$rootScope.Scope#methods_$on listeners} listening for `name` event on this scope get
* notified. Afterwards, the event traverses upwards toward the root scope and calls all
* registered listeners along the way. The event will stop propagating if one of the listeners
* cancels it.
*
* Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
* Any exception emitted from the {@link ng.$rootScope.Scope#methods_$on listeners} will be passed
* onto the {@link ng.$exceptionHandler $exceptionHandler} service.
*
* @param {string} name Event name to emit.
* @param {...*} args Optional set of arguments which will be passed onto the event listeners.
* @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}).
* @return {Object} Event object (see {@link ng.$rootScope.Scope#methods_$on}).
*/
$emit: function(name, args) {
var empty = [],
@@ -946,19 +968,19 @@ function $RootScopeProvider(){
*
* @description
* Dispatches an event `name` downwards to all child scopes (and their children) notifying the
* registered {@link ng.$rootScope.Scope#$on} listeners.
* registered {@link ng.$rootScope.Scope#methods_$on} listeners.
*
* The event life cycle starts at the scope on which `$broadcast` was called. All
* {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
* {@link ng.$rootScope.Scope#methods_$on listeners} listening for `name` event on this scope get
* notified. Afterwards, the event propagates to all direct and indirect scopes of the current
* scope and calls all registered listeners along the way. The event cannot be canceled.
*
* Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
* Any exception emitted from the {@link ng.$rootScope.Scope#methods_$on listeners} will be passed
* onto the {@link ng.$exceptionHandler $exceptionHandler} service.
*
* @param {string} name Event name to broadcast.
* @param {...*} args Optional set of arguments which will be passed onto the event listeners.
* @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
* @return {Object} Event object, see {@link ng.$rootScope.Scope#methods_$on}
*/
$broadcast: function(name, args) {
var target = this,
+74
View File
@@ -0,0 +1,74 @@
'use strict';
/**
* @description
* Private service to sanitize uris for links and images. Used by $compile and $sanitize.
*/
function $$SanitizeUriProvider() {
var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/,
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
/**
* @description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during a[href] sanitization.
*
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
*
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
*
* @param {RegExp=} regexp New regexp to whitelist urls with.
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
* chaining otherwise.
*/
this.aHrefSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
aHrefSanitizationWhitelist = regexp;
return this;
}
return aHrefSanitizationWhitelist;
};
/**
* @description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
* urls during img[src] sanitization.
*
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
*
* Any url about to be assigned to img[src] via data-binding is first normalized and turned into
* an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
*
* @param {RegExp=} regexp New regexp to whitelist urls with.
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
* chaining otherwise.
*/
this.imgSrcSanitizationWhitelist = function(regexp) {
if (isDefined(regexp)) {
imgSrcSanitizationWhitelist = regexp;
return this;
}
return imgSrcSanitizationWhitelist;
};
this.$get = function() {
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;
}
}
return uri;
};
};
}
+4 -44
View File
@@ -7,11 +7,6 @@
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = document.createElement("a");
/*
Matches paths for file protocol on windows,
such as /C:/foo/bar, and captures only /foo/bar.
*/
var windowsFilePathExp = /^\/?.*?:(\/.*)/;
var originUrl = urlResolve(window.location.href, true);
@@ -68,8 +63,7 @@ var originUrl = urlResolve(window.location.href, true);
*
*/
function urlResolve(url, base) {
var href = url,
pathname;
var href = url;
if (msie) {
// Normalize before parse. Refer Implementation Notes on why this is
@@ -80,21 +74,6 @@ function urlResolve(url, base) {
urlParsingNode.setAttribute('href', href);
/*
* In Windows, on an anchor node on documents loaded from
* the filesystem, the browser will return a pathname
* prefixed with the drive name ('/C:/path') when a
* pathname without a drive is set:
* * a.setAttribute('href', '/foo')
* * a.pathname === '/C:/foo' //true
*
* Inside of Angular, we're always using pathnames that
* do not include drive names for routing.
*/
pathname = removeWindowsDriveName(urlParsingNode.pathname, url, base);
pathname = (pathname.charAt(0) === '/') ? pathname : '/' + pathname;
// urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
return {
href: urlParsingNode.href,
@@ -104,11 +83,12 @@ function urlResolve(url, base) {
hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
hostname: urlParsingNode.hostname,
port: urlParsingNode.port,
pathname: pathname
pathname: (urlParsingNode.pathname.charAt(0) === '/')
? urlParsingNode.pathname
: '/' + urlParsingNode.pathname
};
}
/**
* Parse a request URL and determine whether this is a same-origin request as the application document.
*
@@ -121,23 +101,3 @@ function urlIsSameOrigin(requestUrl) {
return (parsed.protocol === originUrl.protocol &&
parsed.host === originUrl.host);
}
function removeWindowsDriveName (path, url, base) {
var firstPathSegmentMatch;
//Get the relative path from the input URL.
if (url.indexOf(base) === 0) {
url = url.replace(base, '');
}
/*
* The input URL intentionally contains a
* first path segment that ends with a colon.
*/
if (windowsFilePathExp.exec(url)) {
return path;
}
firstPathSegmentMatch = windowsFilePathExp.exec(path);
return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
}
+4 -2
View File
@@ -20,13 +20,15 @@
<doc:source>
<script>
function Ctrl($scope, $window) {
$scope.$window = $window;
$scope.greeting = 'Hello, World!';
$scope.doGreeting = function(greeting) {
$window.alert(greeting);
};
}
</script>
<div ng-controller="Ctrl">
<input type="text" ng-model="greeting" />
<button ng-click="$window.alert(greeting)">ALERT</button>
<button ng-click="doGreeting(greeting)">ALERT</button>
</div>
</doc:source>
<doc:scenario>
+69 -29
View File
@@ -190,7 +190,7 @@
*
* <pre>
* //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your application.
* var ngModule = angular.module('YourApp', []);
* var ngModule = angular.module('YourApp', ['ngAnimate']);
* ngModule.animation('.my-crazy-animation', function() {
* return {
* enter: function(element, done) {
@@ -199,8 +199,8 @@
* //this (optional) function will be called when the animation
* //completes or when the animation is cancelled (the cancelled
* //flag will be set to true if cancelled).
* }
* }
* };
* },
* leave: function(element, done) { },
* move: function(element, done) { },
*
@@ -215,7 +215,7 @@
*
* //animation that can be triggered after the class is removed
* removeClass: function(element, className, done) { }
* }
* };
* });
* </pre>
*
@@ -258,14 +258,34 @@ angular.module('ngAnimate', ['ng'])
var NG_ANIMATE_CLASS_NAME = 'ng-animate';
var rootAnimateState = {running: true};
function extractElementNode(element) {
for(var i = 0; i < element.length; i++) {
var elm = element[i];
if(elm.nodeType == ELEMENT_NODE) {
return elm;
}
}
}
function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// disable animations during bootstrap, but once we bootstrapped, enable animations
// disable animations during bootstrap, but once we bootstrapped, wait again
// for another digest until enabling animations. The reason why we digest twice
// is because all structural animations (enter, leave and move) all perform a
// post digest operation before animating. If we only wait for a single digest
// to pass then the structural animation would render its animation on page load.
// (which is what we're trying to avoid when the application first boots up.)
$rootScope.$$postDigest(function() {
rootAnimateState.running = false;
$rootScope.$$postDigest(function() {
rootAnimateState.running = false;
});
});
function lookup(name) {
@@ -363,7 +383,7 @@ angular.module('ngAnimate', ['ng'])
* Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
* the animation is started, the following CSS classes will be added for the duration of the animation:
*
* Below is a breakdown of each step that occurs during enter animation:
* Below is a breakdown of each step that occurs during leave animation:
*
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------|---------------------------------------------|
@@ -549,7 +569,16 @@ angular.module('ngAnimate', ['ng'])
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
var currentClassName = element.attr('class') || '';
var node = extractElementNode(element);
//transcluded directives may sometimes fire an animation using only comment nodes
//best to catch this early on to prevent any animation operations from occurring
if(!node) {
fireDOMOperation();
closeAnimation();
return;
}
var currentClassName = node.className;
var classes = currentClassName + ' ' + className;
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
@@ -753,11 +782,7 @@ angular.module('ngAnimate', ['ng'])
}
function cancelChildAnimations(element) {
var node = element[0];
if(node.nodeType != ELEMENT_NODE) {
return;
}
var node = extractElementNode(element);
forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
element = angular.element(element);
var data = element.data(NG_ANIMATE_STATE);
@@ -781,7 +806,7 @@ angular.module('ngAnimate', ['ng'])
}
function cleanup(element) {
if(element[0] == $rootElement[0]) {
if(isMatchingElement(element, $rootElement)) {
if(!rootAnimateState.disabled) {
rootAnimateState.running = false;
rootAnimateState.structural = false;
@@ -795,7 +820,7 @@ angular.module('ngAnimate', ['ng'])
function animationsDisabled(element, parentElement) {
if (rootAnimateState.disabled) return true;
if(element[0] == $rootElement[0]) {
if(isMatchingElement(element, $rootElement)) {
return rootAnimateState.disabled || rootAnimateState.running;
}
@@ -805,7 +830,7 @@ angular.module('ngAnimate', ['ng'])
//any animations on it
if(parentElement.length === 0) break;
var isRoot = parentElement[0] == $rootElement[0];
var isRoot = isMatchingElement(parentElement, $rootElement);
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
var result = state && (!!state.disabled || !!state.running);
if(isRoot || result) {
@@ -858,6 +883,7 @@ angular.module('ngAnimate', ['ng'])
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
var NG_ANIMATE_FALLBACK_CLASS_NAME = 'ng-animate-start';
var NG_ANIMATE_FALLBACK_ACTIVE_CLASS_NAME = 'ng-animate-active';
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var lookupCache = {};
var parentCounter = 0;
@@ -952,7 +978,7 @@ angular.module('ngAnimate', ['ng'])
parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
parentID = parentCounter;
}
return parentID + '-' + element[0].className;
return parentID + '-' + extractElementNode(element).className;
}
function animateSetup(element, className) {
@@ -987,7 +1013,6 @@ angular.module('ngAnimate', ['ng'])
return false;
}
var node = element[0];
//temporarily disable the transition so that the enter styles
//don't animate twice (this is here to avoid a bug in Chrome/FF).
var activeClassName = '';
@@ -1017,32 +1042,37 @@ angular.module('ngAnimate', ['ng'])
}
function blockTransitions(element) {
element[0].style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
}
function blockKeyframeAnimations(element) {
element[0].style[ANIMATION_PROP] = 'none 0s';
extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
}
function unblockTransitions(element) {
var node = element[0], prop = TRANSITION_PROP + PROPERTY_KEY;
var prop = TRANSITION_PROP + PROPERTY_KEY;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
}
function unblockKeyframeAnimations(element) {
element[0].style[ANIMATION_PROP] = '';
var prop = ANIMATION_PROP;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
}
function animateRun(element, className, activeAnimationComplete) {
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(!element.hasClass(className) || !data) {
var node = extractElementNode(element);
if(node.className.indexOf(className) == -1 || !data) {
activeAnimationComplete();
return;
}
var node = element[0];
var timings = data.timings;
var stagger = data.stagger;
var maxDuration = data.maxDuration;
@@ -1063,8 +1093,6 @@ angular.module('ngAnimate', ['ng'])
appliedStyles.push(CSS_PREFIX + 'transition-property');
appliedStyles.push(CSS_PREFIX + 'transition-duration');
}
} else {
unblockKeyframeAnimations(element);
}
if(ii > 0) {
@@ -1087,6 +1115,9 @@ angular.module('ngAnimate', ['ng'])
}
if(appliedStyles.length > 0) {
//the element being animated may sometimes contain comment nodes in
//the jqLite object, so we're safe to use a single variable to house
//the styles since there is always only one element being animated
var oldStyle = node.getAttribute('style') || '';
node.setAttribute('style', oldStyle + ' ' + style);
}
@@ -1101,6 +1132,7 @@ angular.module('ngAnimate', ['ng'])
element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName);
animateClose(element, className);
var node = extractElementNode(element);
for (var i in appliedStyles) {
node.style.removeProperty(appliedStyles[i]);
}
@@ -1110,6 +1142,11 @@ angular.module('ngAnimate', ['ng'])
event.stopPropagation();
var ev = event.originalEvent || event;
var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
/* Firefox (or possibly just Gecko) likes to not round values up
* when a ms measurement is used for the animation */
var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
/* $manualTimeStamp is a mocked timeStamp value which is set
* within browserTrigger(). This is only here so that tests can
* mock animations properly. Real events fallback to event.timeStamp,
@@ -1117,7 +1154,7 @@ angular.module('ngAnimate', ['ng'])
* We're checking to see if the timeStamp surpasses the expected delay,
* but we're using elapsedTime instead of the timeStamp on the 2nd
* pre-condition since animations sometimes close off early */
if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && ev.elapsedTime >= maxDuration) {
if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
activeAnimationComplete();
}
}
@@ -1167,6 +1204,7 @@ angular.module('ngAnimate', ['ng'])
var cancel = preReflowCancellation;
afterReflow(function() {
unblockTransitions(element);
unblockKeyframeAnimations(element);
//once the reflow is complete then we point cancel to
//the new cancellation function which will remove all of the
//animation properties from the active animation
@@ -1194,12 +1232,12 @@ angular.module('ngAnimate', ['ng'])
}
var parentElement = element.parent();
var clone = angular.element(element[0].cloneNode());
var clone = angular.element(extractElementNode(element).cloneNode());
//make the element super hidden and override any CSS style values
clone.attr('style','position:absolute; top:-9999px; left:-9999px');
clone.removeAttr('id');
clone.html('');
clone.empty();
forEach(oldClasses.split(' '), function(klass) {
clone.removeClass(klass);
@@ -1232,6 +1270,7 @@ angular.module('ngAnimate', ['ng'])
if(cancellationMethod) {
afterReflow(function() {
unblockTransitions(element);
unblockKeyframeAnimations(element);
animationCompleted();
});
return cancellationMethod;
@@ -1248,6 +1287,7 @@ angular.module('ngAnimate', ['ng'])
if(cancellationMethod) {
afterReflow(function() {
unblockTransitions(element);
unblockKeyframeAnimations(element);
animationCompleted();
});
return cancellationMethod;
+198 -207
View File
@@ -1,13 +1,5 @@
'use strict';
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*
* TODO(vojta): wrap whole file into closure during build
*/
/**
* @ngdoc overview
* @name angular.mock
@@ -560,210 +552,208 @@ angular.mock.$IntervalProvider = function() {
* This directive should go inside the anonymous function but a bug in JSHint means that it would
* not be enacted early enough to prevent the warning.
*/
(function() {
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
function jsonStringToDate(string) {
var match;
if (match = string.match(R_ISO8061_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour,
int(match[5]||0) - tzMin,
int(match[6]||0),
int(match[7]||0));
return date;
function jsonStringToDate(string) {
var match;
if (match = string.match(R_ISO8061_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
return string;
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour,
int(match[5]||0) - tzMin,
int(match[6]||0),
int(match[7]||0));
return date;
}
return string;
}
function int(str) {
return parseInt(str, 10);
}
function padNumber(num, digits, trim) {
var neg = '';
if (num < 0) {
neg = '-';
num = -num;
}
num = '' + num;
while(num.length < digits) num = '0' + num;
if (trim)
num = num.substr(num.length - digits);
return neg + num;
function int(str) {
return parseInt(str, 10);
}
function padNumber(num, digits, trim) {
var neg = '';
if (num < 0) {
neg = '-';
num = -num;
}
num = '' + num;
while(num.length < digits) num = '0' + num;
if (trim)
num = num.substr(num.length - digits);
return neg + num;
}
/**
* @ngdoc object
* @name angular.mock.TzDate
* @description
*
* *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
*
* Mock of the Date type which has its timezone specified via constructor arg.
*
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
* offset, so that we can test code that depends on local timezone settings without dependency on
* the time zone settings of the machine where the code is running.
*
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
*
* @example
* !!!! WARNING !!!!!
* This is not a complete Date object so only methods that were implemented can be called safely.
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
*
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
* incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object".
*
* <pre>
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
* newYearInBratislava.getTimezoneOffset() => -60;
* newYearInBratislava.getFullYear() => 2010;
* newYearInBratislava.getMonth() => 0;
* newYearInBratislava.getDate() => 1;
* newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0;
* newYearInBratislava.getSeconds() => 0;
* </pre>
*
*/
angular.mock.TzDate = function (offset, timestamp) {
var self = new Date(0);
if (angular.isString(timestamp)) {
var tsStr = timestamp;
/**
* @ngdoc object
* @name angular.mock.TzDate
* @description
*
* *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
*
* Mock of the Date type which has its timezone specified via constructor arg.
*
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
* offset, so that we can test code that depends on local timezone settings without dependency on
* the time zone settings of the machine where the code is running.
*
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
*
* @example
* !!!! WARNING !!!!!
* This is not a complete Date object so only methods that were implemented can be called safely.
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
*
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
* incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object".
*
* <pre>
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
* newYearInBratislava.getTimezoneOffset() => -60;
* newYearInBratislava.getFullYear() => 2010;
* newYearInBratislava.getMonth() => 0;
* newYearInBratislava.getDate() => 1;
* newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0;
* newYearInBratislava.getSeconds() => 0;
* </pre>
*
*/
angular.mock.TzDate = function (offset, timestamp) {
var self = new Date(0);
if (angular.isString(timestamp)) {
var tsStr = timestamp;
self.origDate = jsonStringToDate(timestamp);
self.origDate = jsonStringToDate(timestamp);
timestamp = self.origDate.getTime();
if (isNaN(timestamp))
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
} else {
self.origDate = new Date(timestamp);
}
var localOffset = new Date(timestamp).getTimezoneOffset();
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
self.date = new Date(timestamp + self.offsetDiff);
self.getTime = function() {
return self.date.getTime() - self.offsetDiff;
};
self.toLocaleDateString = function() {
return self.date.toLocaleDateString();
};
self.getFullYear = function() {
return self.date.getFullYear();
};
self.getMonth = function() {
return self.date.getMonth();
};
self.getDate = function() {
return self.date.getDate();
};
self.getHours = function() {
return self.date.getHours();
};
self.getMinutes = function() {
return self.date.getMinutes();
};
self.getSeconds = function() {
return self.date.getSeconds();
};
self.getMilliseconds = function() {
return self.date.getMilliseconds();
};
self.getTimezoneOffset = function() {
return offset * 60;
};
self.getUTCFullYear = function() {
return self.origDate.getUTCFullYear();
};
self.getUTCMonth = function() {
return self.origDate.getUTCMonth();
};
self.getUTCDate = function() {
return self.origDate.getUTCDate();
};
self.getUTCHours = function() {
return self.origDate.getUTCHours();
};
self.getUTCMinutes = function() {
return self.origDate.getUTCMinutes();
};
self.getUTCSeconds = function() {
return self.origDate.getUTCSeconds();
};
self.getUTCMilliseconds = function() {
return self.origDate.getUTCMilliseconds();
};
self.getDay = function() {
return self.date.getDay();
};
// provide this method only on browsers that already have it
if (self.toISOString) {
self.toISOString = function() {
return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
padNumber(self.origDate.getUTCDate(), 2) + 'T' +
padNumber(self.origDate.getUTCHours(), 2) + ':' +
padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z';
timestamp = self.origDate.getTime();
if (isNaN(timestamp))
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
}
} else {
self.origDate = new Date(timestamp);
}
//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getUTCDay',
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
var localOffset = new Date(timestamp).getTimezoneOffset();
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
self.date = new Date(timestamp + self.offsetDiff);
angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() {
throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
};
});
return self;
self.getTime = function() {
return self.date.getTime() - self.offsetDiff;
};
//make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype;
})();
self.toLocaleDateString = function() {
return self.date.toLocaleDateString();
};
self.getFullYear = function() {
return self.date.getFullYear();
};
self.getMonth = function() {
return self.date.getMonth();
};
self.getDate = function() {
return self.date.getDate();
};
self.getHours = function() {
return self.date.getHours();
};
self.getMinutes = function() {
return self.date.getMinutes();
};
self.getSeconds = function() {
return self.date.getSeconds();
};
self.getMilliseconds = function() {
return self.date.getMilliseconds();
};
self.getTimezoneOffset = function() {
return offset * 60;
};
self.getUTCFullYear = function() {
return self.origDate.getUTCFullYear();
};
self.getUTCMonth = function() {
return self.origDate.getUTCMonth();
};
self.getUTCDate = function() {
return self.origDate.getUTCDate();
};
self.getUTCHours = function() {
return self.origDate.getUTCHours();
};
self.getUTCMinutes = function() {
return self.origDate.getUTCMinutes();
};
self.getUTCSeconds = function() {
return self.origDate.getUTCSeconds();
};
self.getUTCMilliseconds = function() {
return self.origDate.getUTCMilliseconds();
};
self.getDay = function() {
return self.date.getDay();
};
// provide this method only on browsers that already have it
if (self.toISOString) {
self.toISOString = function() {
return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
padNumber(self.origDate.getUTCDate(), 2) + 'T' +
padNumber(self.origDate.getUTCHours(), 2) + ':' +
padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z';
};
}
//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getUTCDay',
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() {
throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock");
};
});
return self;
};
//make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype;
/* jshint +W101 */
angular.mock.animate = angular.module('mock.animate', ['ng'])
@@ -1097,7 +1087,8 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
var definitions = [],
expectations = [],
responses = [],
responsesPush = angular.bind(responses, responses.push);
responsesPush = angular.bind(responses, responses.push),
copy = angular.copy;
function createResponse(status, data, headers) {
if (angular.isFunction(status)) return status;
@@ -1129,7 +1120,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
function handleResponse() {
var response = wrapped.response(method, url, data, headers);
xhr.$$respHeaders = response[2];
callback(response[0], response[1], xhr.getAllResponseHeaders());
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders());
}
function handleTimeout() {
@@ -1919,9 +1910,13 @@ angular.mock.clearDataCache = function() {
(window.jasmine || window.mocha) && (function(window) {
if(window.jasmine || window.mocha) {
var currentSpec = null,
isSpecRunning = function() {
return currentSpec && (window.mocha || currentSpec.queue.running);
};
var currentSpec = null;
beforeEach(function() {
currentSpec = this;
@@ -1954,10 +1949,6 @@ angular.mock.clearDataCache = function() {
angular.callbacks.counter = 0;
});
function isSpecRunning() {
return currentSpec && (window.mocha || currentSpec.queue.running);
}
/**
* @ngdoc function
* @name angular.mock.module
@@ -2112,4 +2103,4 @@ angular.mock.clearDataCache = function() {
}
}
};
})(window);
}
+22 -3
View File
@@ -24,6 +24,25 @@ function lookupDottedPath(obj, path) {
return obj;
}
/**
* Create a shallow copy of an object and clear other fields from the destination
*/
function shallowClearAndCopy(src, dst) {
dst = dst || {};
angular.forEach(dst, function(value, key){
delete dst[key];
});
for (var key in src) {
if (src.hasOwnProperty(key) && key.substr(0, 2) !== '$$') {
dst[key] = src[key];
}
}
return dst;
}
/**
* @ngdoc overview
* @name ngResource
@@ -393,7 +412,7 @@ angular.module('ngResource', ['ng']).
}
function Resource(value){
copy(value || {}, this);
shallowClearAndCopy(value || {}, this);
}
forEach(actions, function(action, name) {
@@ -465,7 +484,7 @@ angular.module('ngResource', ['ng']).
if (data) {
// Need to convert action.isArray to boolean in case it is undefined
// jshint -W018
if ( angular.isArray(data) !== (!!action.isArray) ) {
if (angular.isArray(data) !== (!!action.isArray)) {
throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' +
'response to contain an {0} but got an {1}',
action.isArray?'array':'object', angular.isArray(data)?'array':'object');
@@ -477,7 +496,7 @@ angular.module('ngResource', ['ng']).
value.push(new Resource(item));
});
} else {
copy(data, value);
shallowClearAndCopy(data, value);
value.$promise = promise;
}
}
+47 -27
View File
@@ -1,6 +1,8 @@
'use strict';
ngRouteModule.directive('ngView', ngViewFactory);
ngRouteModule.directive('ngView', ngViewFillContentFactory);
/**
* @ngdoc directive
@@ -166,8 +168,8 @@ ngRouteModule.directive('ngView', ngViewFactory);
* @description
* Emitted every time the ngView content is reloaded.
*/
ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate'];
function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) {
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
function ngViewFactory( $route, $anchorScroll, $animate) {
return {
restrict: 'ECA',
terminal: true,
@@ -199,6 +201,7 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
if (template) {
var newScope = scope.$new();
var current = $route.current;
// Note: This will also link all children of ng-view that were contained in the original
// html. If that content contains controllers, ... they could pollute/change the scope.
@@ -206,34 +209,18 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
// Note: We can't remove them in the cloneAttchFn of $transclude as that
// function is called before linking the content, which would apply child
// directives to non existing elements.
var clone = $transclude(newScope, angular.noop);
clone.html(template);
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
if (angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
}
});
cleanupLastView();
});
cleanupLastView();
var link = $compile(clone.contents()),
current = $route.current;
currentScope = current.scope = newScope;
currentElement = clone;
if (current.controller) {
locals.$scope = currentScope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
currentScope[current.controllerAs] = controller;
}
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
}
link(currentScope);
currentScope = current.scope = newScope;
currentScope.$emit('$viewContentLoaded');
currentScope.$eval(onloadExp);
} else {
@@ -243,3 +230,36 @@ function ngViewFactory( $route, $anchorScroll, $compile, $controller,
}
};
}
// This directive is called during the $transclude call of the first `ngView` directive.
// It will replace and compile the content of the element with the loaded template.
// We need this directive so that the element content is already filled when
// the link function of another directive on the same element as ngView
// is called.
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
function ngViewFillContentFactory($compile, $controller, $route) {
return {
restrict: 'ECA',
priority: -400,
link: function(scope, $element) {
var current = $route.current,
locals = current.locals;
$element.html(locals.$template);
var link = $compile($element.contents());
if (current.controller) {
locals.$scope = scope;
var controller = $controller(current.controller, locals);
if (current.controllerAs) {
scope[current.controllerAs] = controller;
}
$element.data('$ngControllerController', controller);
$element.children().data('$ngControllerController', controller);
}
link(scope);
}
};
}
+4 -4
View File
@@ -52,13 +52,13 @@ function $RouteProvider(){
* `$location.path` will be updated to add or drop the trailing slash to exactly match the
* route definition.
*
* * `path` can contain named groups starting with a colon (`:name`). All characters up
* * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up
* to the next slash are matched and stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain named groups starting with a colon and ending with a star (`:name*`).
* All characters are eagerly stored in `$routeParams` under the given `name`
* * `path` can contain named groups starting with a colon and ending with a star:
* e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name`
* when the route matches.
* * `path` can contain optional named groups with a question mark (`:name?`).
* * `path` can contain optional named groups with a question mark: e.g.`:name?`.
*
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
* `/color/brown/largecode/code/with/slashs/edit` and extract:
+28 -16
View File
@@ -1,6 +1,6 @@
'use strict';
/* global htmlSanitizeWriter: false */
/* global sanitizeText: false */
/**
* @ngdoc filter
@@ -100,7 +100,7 @@
</doc:scenario>
</doc:example>
*/
angular.module('ngSanitize').filter('linky', function() {
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
MAILTO_REGEXP = /^mailto:/;
@@ -110,28 +110,40 @@ angular.module('ngSanitize').filter('linky', function() {
var match;
var raw = text;
var html = [];
// TODO(vojta): use $sanitize instead
var writer = htmlSanitizeWriter(html);
var url;
var i;
var properties = {};
if (angular.isDefined(target)) {
properties.target = target;
}
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
properties.href = url;
writer.start('a', properties);
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
writer.chars(raw);
return html.join('');
addText(raw);
return $sanitize(html.join(''));
function addText(text) {
if (!text) {
return;
}
html.push(sanitizeText(text));
}
function addLink(url, text) {
html.push('<a ');
if (angular.isDefined(target)) {
html.push('target="');
html.push(target);
html.push('" ');
}
html.push('href="');
html.push(url);
html.push('">');
addText(text);
html.push('</a>');
}
};
});
}]);
+44 -11
View File
@@ -46,6 +46,8 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
@@ -128,11 +130,24 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
</doc:scenario>
</doc:example>
*/
var $sanitize = function(html) {
function $SanitizeProvider() {
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
}));
return buf.join('');
};
}];
}
function sanitizeText(chars) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf));
return buf.join('');
};
var writer = htmlSanitizeWriter(buf, angular.noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
@@ -145,7 +160,6 @@ var START_TAG_REGEXP =
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
@@ -346,15 +360,32 @@ function htmlParser( html, handler ) {
}
}
var hiddenPre=document.createElement("pre");
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
var hiddenPre=document.createElement("pre");
function decodeEntities(value) {
hiddenPre.innerHTML=value.replace(/</g,"&lt;");
return hiddenPre.innerText || hiddenPre.textContent || '';
if (!value) { return ''; }
// Note: IE8 does not preserve spaces at the start/end of innerHTML
// so we must capture them and reattach them afterward
var parts = spaceRe.exec(value);
var spaceBefore = parts[1];
var spaceAfter = parts[3];
var content = parts[2];
if (content) {
hiddenPre.innerHTML=content.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary
// reflows. However, IE<9 don't support textContent so the innerText
// fallback is necessary.
content = 'textContent' in hiddenPre ?
hiddenPre.textContent : hiddenPre.innerText;
}
return spaceBefore + content + spaceAfter;
}
/**
@@ -384,7 +415,7 @@ function encodeEntities(value) {
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf){
function htmlSanitizeWriter(buf, uriValidator){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
@@ -398,7 +429,9 @@ function htmlSanitizeWriter(buf){
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
if (validAttrs[lkey]===true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
@@ -430,4 +463,4 @@ function htmlSanitizeWriter(buf){
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).value('$sanitize', $sanitize);
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2012 Google, Inc. http://angularjs.org
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document){
+13
View File
@@ -1079,4 +1079,17 @@ describe('angular', function() {
}
});
describe('isElement', function() {
it('should return a boolean value', inject(function($compile, $document, $rootScope) {
var element = $compile('<p>Hello, world!</p>')($rootScope),
body = $document.find('body')[0],
expected = [false, false, false, false, false, false, false, true, true],
tests = [null, undefined, "string", 1001, {}, 0, false, body, element];
angular.forEach(tests, function(value, idx) {
var result = angular.isElement(value);
expect(typeof result).toEqual('boolean');
expect(result).toEqual(expected[idx]);
});
}));
});
});
+1 -2
View File
@@ -29,8 +29,7 @@ beforeEach(function() {
bindJQuery();
}
angular.element(document.body).html('').removeData();
angular.element(document.body).empty().removeData();
});
afterEach(function() {
+29 -2
View File
@@ -322,7 +322,7 @@ describe('jqLite', function() {
});
it('should emit $destroy event if an element is removed via html()', inject(function(log) {
it('should emit $destroy event if an element is removed via html(\'\')', inject(function(log) {
var element = jqLite('<div><span>x</span></div>');
element.find('span').on('$destroy', log.fn('destroyed'));
@@ -333,6 +333,17 @@ describe('jqLite', function() {
}));
it('should emit $destroy event if an element is removed via empty()', inject(function(log) {
var element = jqLite('<div><span>x</span></div>');
element.find('span').on('$destroy', log.fn('destroyed'));
element.empty();
expect(element.html()).toBe('');
expect(log).toEqual('destroyed');
}));
it('should retrieve all data if called without params', function() {
var element = jqLite(a);
expect(element.data()).toEqual({});
@@ -786,7 +797,7 @@ describe('jqLite', function() {
});
it('should read/write value', function() {
it('should read/write a value', function() {
var element = jqLite('<div>abc</div>');
expect(element.length).toEqual(1);
expect(element[0].innerHTML).toEqual('abc');
@@ -797,6 +808,16 @@ describe('jqLite', function() {
});
describe('empty', function() {
it('should write a value', function() {
var element = jqLite('<div>abc</div>');
expect(element.length).toEqual(1);
expect(element.empty() == element).toBeTruthy();
expect(element.html()).toEqual('');
});
});
describe('on', function() {
it('should bind to window on hashchange', function() {
if (jqLite.fn) return; // don't run in jQuery
@@ -1335,6 +1356,12 @@ describe('jqLite', function() {
expect(innerDiv.length).toEqual(1);
expect(innerDiv.html()).toEqual('text');
});
it('should find child by name and not care about text nodes', function() {
var divs = jqLite('<div><span>aa</span></div>text<div><span>bb</span></div>');
var innerSpan = divs.find('span');
expect(innerSpan.length).toEqual(2);
});
});
+184 -265
View File
@@ -170,26 +170,26 @@ describe('$compile', function() {
// First with only elements at the top level
element = jqLite('<div><div></div></div>');
$compile(element.contents())($rootScope);
element.html('');
element.empty();
expect(calcCacheSize()).toEqual(0);
// Next with non-empty text nodes at the top level
// (in this case the compiler will wrap them in a <span>)
element = jqLite('<div>xxx</div>');
$compile(element.contents())($rootScope);
element.html('');
element.empty();
expect(calcCacheSize()).toEqual(0);
// Next with comment nodes at the top level
element = jqLite('<div><!-- comment --></div>');
$compile(element.contents())($rootScope);
element.html('');
element.empty();
expect(calcCacheSize()).toEqual(0);
// Finally with empty text nodes at the top level
element = jqLite('<div> \n<div></div> </div>');
$compile(element.contents())($rootScope);
element.html('');
element.empty();
expect(calcCacheSize()).toEqual(0);
});
@@ -969,6 +969,32 @@ describe('$compile', function() {
});
});
it('should resolve widgets after cloning in append mode without $templateCache', function() {
module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
});
inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
$exceptionHandler) {
$httpBackend.expect('GET', 'cau.html').respond('<span>{{name}}</span>');
$rootScope.name = 'Elvis';
var template = $compile('<div class="cau"></div>');
var e1;
var e2;
e1 = template($rootScope.$new(), noop); // clone
expect(e1.text()).toEqual('');
$httpBackend.flush();
e2 = template($rootScope.$new(), noop); // clone
$rootScope.$digest();
expect(e1.text()).toEqual('Elvis');
expect(e2.text()).toEqual('Elvis');
dealoc(e1);
dealoc(e2);
});
});
it('should resolve widgets after cloning in inline mode', function() {
module(function($exceptionHandlerProvider) {
@@ -1010,6 +1036,33 @@ describe('$compile', function() {
});
});
it('should resolve widgets after cloning in inline mode without $templateCache', function() {
module(function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
});
inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser,
$exceptionHandler) {
$httpBackend.expect('GET', 'cau.html').respond('<span>{{name}}</span>');
$rootScope.name = 'Elvis';
var template = $compile('<div class="i-cau"></div>');
var e1;
var e2;
e1 = template($rootScope.$new(), noop); // clone
expect(e1.text()).toEqual('');
$httpBackend.flush();
e2 = template($rootScope.$new(), noop); // clone
$rootScope.$digest();
expect(e1.text()).toEqual('Elvis');
expect(e2.text()).toEqual('Elvis');
dealoc(e1);
dealoc(e2);
});
});
it('should be implicitly terminal and not compile placeholder content in append', inject(
function($compile, $templateCache, $rootScope, log) {
@@ -2386,6 +2439,24 @@ describe('$compile', function() {
expect(componentScope.refAlias).toBe($rootScope.name);
}));
it('should not break if local and origin both change to the same value', inject(function() {
$rootScope.name = 'aaa';
compile('<div><span my-component ref="name">');
//change both sides to the same item withing the same digest cycle
componentScope.ref = 'same';
$rootScope.name = 'same';
$rootScope.$apply();
//change origin back to it's previous value
$rootScope.name = 'aaa';
$rootScope.$apply();
expect($rootScope.name).toBe('aaa');
expect(componentScope.ref).toBe('aaa');
}));
it('should complain on non assignable changes', inject(function() {
compile('<div><span my-component ref="\'hello \' + name">');
$rootScope.name = 'world';
@@ -2421,6 +2492,62 @@ describe('$compile', function() {
expect(lastRefValueInParent).toBe('new');
}));
describe('literal objects', function() {
it('should copy parent changes', inject(function() {
compile('<div><span my-component reference="{name: name}">');
$rootScope.name = 'a';
$rootScope.$apply();
expect(componentScope.reference).toEqual({name: 'a'});
$rootScope.name = 'b';
$rootScope.$apply();
expect(componentScope.reference).toEqual({name: 'b'});
}));
it('should not change the component when parent does not change', inject(function() {
compile('<div><span my-component reference="{name: name}">');
$rootScope.name = 'a';
$rootScope.$apply();
var lastComponentValue = componentScope.reference;
$rootScope.$apply();
expect(componentScope.reference).toBe(lastComponentValue);
}));
it('should complain when the component changes', inject(function() {
compile('<div><span my-component reference="{name: name}">');
$rootScope.name = 'a';
$rootScope.$apply();
componentScope.reference = {name: 'b'};
expect(function() {
$rootScope.$apply();
}).toThrowMinErr("$compile", "nonassign", "Expression '{name: name}' used with directive 'myComponent' is non-assignable!");
}));
it('should work for primitive literals', inject(function() {
test('1', 1);
test('null', null);
test('undefined', undefined);
test("'someString'", 'someString');
function test(literalString, literalValue) {
compile('<div><span my-component reference="'+literalString+'">');
$rootScope.$apply();
expect(componentScope.reference).toBe(literalValue);
dealoc(element);
}
}));
});
});
@@ -3834,6 +3961,7 @@ describe('$compile', function() {
describe('img[src] sanitization', function() {
it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = 'http://example.com/image.png';
@@ -3845,127 +3973,6 @@ describe('$compile', function() {
expect(element.attr('src')).toEqual('http://example.com/image2.png');
}));
it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should sanitize non-image data: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
$rootScope.$apply();
expect(element.attr('src')).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
$rootScope.testUrl = "data:,foo";
$rootScope.$apply();
expect(element.attr('src')).toBe("unsafe:data:,foo");
}));
it('should not sanitize data: URIs for images', inject(function($compile, $rootScope) {
element = $compile('<img src="{{dataUri}}"></img>')($rootScope);
// image data uri
// ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
$rootScope.dataUri = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
$rootScope.$apply();
expect(element.attr('src')).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
}));
// Fails on IE <= 10 with "TypeError: Access is denied" when trying to set img[src]
if (!msie || msie > 10) {
it('should sanitize mailto: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "mailto:foo@bar.com";
$rootScope.$apply();
expect(element.attr('src')).toBe('unsafe:mailto:foo@bar.com');
}));
}
it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
// case-sensitive
$rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
$rootScope.testUrl = "java\u0009script:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// space before
$rootScope.testUrl = " javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
$rootScope.testUrl = " \u000e javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// post-fixed with proper url
$rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
$rootScope.$apply();
expect(element[0].src).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
}));
it('should sanitize ng-src bindings as well', inject(function($compile, $rootScope) {
element = $compile('<img ng-src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should not sanitize valid urls', inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "foo/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('foo/bar');
$rootScope.testUrl = "/foo/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('/foo/bar');
$rootScope.testUrl = "../foo/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('../foo/bar');
$rootScope.testUrl = "#foo";
$rootScope.$apply();
expect(element.attr('src')).toBe('#foo');
$rootScope.testUrl = "http://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('http://foo.com/bar');
$rootScope.testUrl = " http://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe(' http://foo.com/bar');
$rootScope.testUrl = "https://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('https://foo.com/bar');
$rootScope.testUrl = "ftp://foo.com/bar";
$rootScope.$apply();
expect(element.attr('src')).toBe('ftp://foo.com/bar');
$rootScope.testUrl = "file:///foo/bar.html";
$rootScope.$apply();
expect(element.attr('src')).toBe('file:///foo/bar.html');
}));
it('should not sanitize attributes other than src', inject(function($compile, $rootScope) {
element = $compile('<img title="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -3974,141 +3981,42 @@ describe('$compile', function() {
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
}));
it('should use $$sanitizeUriProvider for reconfiguration of the src whitelist', function() {
module(function($compileProvider, $$sanitizeUriProvider) {
var newRe = /javascript:/,
returnVal;
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe($$sanitizeUriProvider.imgSrcSanitizationWhitelist());
it('should allow reconfiguration of the src whitelist', function() {
module(function($compileProvider) {
expect($compileProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
var returnVal = $compileProvider.imgSrcSanitizationWhitelist(/javascript:/);
returnVal = $compileProvider.imgSrcSanitizationWhitelist(newRe);
expect(returnVal).toBe($compileProvider);
expect($$sanitizeUriProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
});
inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
// Fails on IE <= 11 with "TypeError: Object doesn't support this property or method" when
// trying to set img[src]
if (!msie || msie > 11) {
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('src')).toBe('javascript:doEvilStuff()');
}
$rootScope.testUrl = "http://recon/figured";
$rootScope.$apply();
expect(element.attr('src')).toBe('unsafe:http://recon/figured');
inject(function() {
// needed to the module definition above is run...
});
});
it('should use $$sanitizeUri', function() {
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
module(function($provide) {
$provide.value('$$sanitizeUri', $$sanitizeUri);
});
inject(function($compile, $rootScope) {
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
$rootScope.testUrl = "someUrl";
$$sanitizeUri.andReturn('someSanitizedUrl');
$rootScope.$apply();
expect(element.attr('src')).toBe('someSanitizedUrl');
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true);
});
});
});
describe('a[href] sanitization', function() {
it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('href')).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should sanitize data: urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "data:evilPayload";
$rootScope.$apply();
expect(element.attr('href')).toBe('unsafe:data:evilPayload');
}));
it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
// case-sensitive
$rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
$rootScope.testUrl = "java\u0009script:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// space before
$rootScope.testUrl = " javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
$rootScope.testUrl = " \u000e javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
// post-fixed with proper url
$rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
$rootScope.$apply();
expect(element[0].href).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
}));
it('should sanitize ngHref bindings as well', inject(function($compile, $rootScope) {
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element[0].href).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should not sanitize valid urls', inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('foo/bar');
$rootScope.testUrl = "/foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('/foo/bar');
$rootScope.testUrl = "../foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('../foo/bar');
$rootScope.testUrl = "#foo";
$rootScope.$apply();
expect(element.attr('href')).toBe('#foo');
$rootScope.testUrl = "http://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('http://foo/bar');
$rootScope.testUrl = " http://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe(' http://foo/bar');
$rootScope.testUrl = "https://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('https://foo/bar');
$rootScope.testUrl = "ftp://foo/bar";
$rootScope.$apply();
expect(element.attr('href')).toBe('ftp://foo/bar');
$rootScope.testUrl = "mailto:foo@bar.com";
$rootScope.$apply();
expect(element.attr('href')).toBe('mailto:foo@bar.com');
$rootScope.testUrl = "file:///foo/bar.html";
$rootScope.$apply();
expect(element.attr('href')).toBe('file:///foo/bar.html');
}));
it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) {
element = $compile('<div href="{{testUrl}}"></div>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -4117,7 +4025,6 @@ describe('$compile', function() {
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
}));
it('should not sanitize attributes other than href', inject(function($compile, $rootScope) {
element = $compile('<a title="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
@@ -4126,26 +4033,38 @@ describe('$compile', function() {
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
}));
it('should use $$sanitizeUriProvider for reconfiguration of the href whitelist', function() {
module(function($compileProvider, $$sanitizeUriProvider) {
var newRe = /javascript:/,
returnVal;
expect($compileProvider.aHrefSanitizationWhitelist()).toBe($$sanitizeUriProvider.aHrefSanitizationWhitelist());
it('should allow reconfiguration of the href whitelist', function() {
module(function($compileProvider) {
expect($compileProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
var returnVal = $compileProvider.aHrefSanitizationWhitelist(/javascript:/);
returnVal = $compileProvider.aHrefSanitizationWhitelist(newRe);
expect(returnVal).toBe($compileProvider);
expect($$sanitizeUriProvider.aHrefSanitizationWhitelist()).toBe(newRe);
expect($compileProvider.aHrefSanitizationWhitelist()).toBe(newRe);
});
inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "javascript:doEvilStuff()";
$rootScope.$apply();
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
$rootScope.testUrl = "http://recon/figured";
$rootScope.$apply();
expect(element.attr('href')).toBe('unsafe:http://recon/figured');
inject(function() {
// needed to the module definition above is run...
});
});
it('should use $$sanitizeUri', function() {
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
module(function($provide) {
$provide.value('$$sanitizeUri', $$sanitizeUri);
});
inject(function($compile, $rootScope) {
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
$rootScope.testUrl = "someUrl";
$$sanitizeUri.andReturn('someSanitizedUrl');
$rootScope.$apply();
expect(element.attr('href')).toBe('someSanitizedUrl');
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
});
});
});
describe('interpolation on HTML DOM event handler attributes onclick, onXYZ, formaction', function() {
+26
View File
@@ -58,4 +58,30 @@ describe('a', function() {
expect(element.text()).toBe('hello@you');
});
it('should not link and hookup an event if href is present at compile', function() {
var jq = jQuery || jqLite;
element = jq('<a href="//a.com">hello@you</a>');
var linker = $compile(element);
spyOn(jq.prototype, 'on');
linker($rootScope);
expect(jq.prototype.on).not.toHaveBeenCalled();
});
it('should not link and hookup an event if name is present at compile', function() {
var jq = jQuery || jqLite;
element = jq('<a name="bobby">hello@you</a>');
var linker = $compile(element);
spyOn(jq.prototype, 'on');
linker($rootScope);
expect(jq.prototype.on).not.toHaveBeenCalled();
});
});
+1 -1
View File
@@ -216,7 +216,7 @@ describe('form', function() {
// yes, I know, scope methods should not do direct DOM manipulation, but I wanted to keep
// this test small. Imagine that the destroy action will cause a model change (e.g.
// $location change) that will cause some directive to destroy the dom (e.g. ngView+$route)
doc.html('');
doc.empty();
destroyed = true;
}
+49
View File
@@ -383,6 +383,29 @@ describe('ngModel', function() {
dealoc(element);
});
});
it('should keep previously defined watches consistent when changes in validity are made',
inject(function($compile, $rootScope) {
var isFormValid;
$rootScope.$watch('myForm.$valid', function(value) { isFormValid = value; });
var element = $compile('<form name="myForm">' +
'<input name="myControl" ng-model="value" required >' +
'</form>')($rootScope);
$rootScope.$apply();
expect(isFormValid).toBe(false);
expect($rootScope.myForm.$valid).toBe(false);
$rootScope.value='value';
$rootScope.$apply();
expect(isFormValid).toBe(true);
expect($rootScope.myForm.$valid).toBe(true);
dealoc(element);
}));
});
@@ -468,6 +491,32 @@ describe('input', function() {
expect(scope.name).toEqual('adam');
});
describe('"change" event', function() {
function assertBrowserSupportsChangeEvent(inputEventSupported) {
// Force browser to report a lack of an 'input' event
$sniffer.hasEvent = function(eventName) {
if (eventName === 'input' && !inputEventSupported) {
return false;
}
return true;
};
compileInput('<input type="text" ng-model="name" name="alias" />');
inputElm.val('mark');
browserTrigger(inputElm, 'change');
expect(scope.name).toEqual('mark');
}
it('should update the model event if the browser does not support the "input" event',function() {
assertBrowserSupportsChangeEvent(false);
});
it('should update the model event if the browser supports the "input" ' +
'event so that form auto complete works',function() {
assertBrowserSupportsChangeEvent(true);
});
});
describe('"paste" and "cut" events', function() {
beforeEach(function() {
// Force browser to report a lack of an 'input' event
+4
View File
@@ -373,6 +373,10 @@ describe('ngClass animations', function() {
// Enable animations by triggering the first item in the postDigest queue
digestQueue.shift()();
// wait for the 2nd animation bootstrap digest to pass
$rootScope.$digest();
digestQueue.shift()();
$rootScope.val = 'crazy';
var element = angular.element('<div ng-class="val"></div>');
jqLite($document[0].body).append($rootElement);
+26 -1
View File
@@ -1,8 +1,11 @@
'use strict';
describe('ngIf', function () {
var $scope, $compile, element;
var $scope, $compile, element, $compileProvider;
beforeEach(module(function(_$compileProvider_) {
$compileProvider = _$compileProvider_;
}));
beforeEach(inject(function ($rootScope, _$compile_) {
$scope = $rootScope.$new();
$compile = _$compile_;
@@ -146,6 +149,28 @@ describe('ngIf', function () {
expect(element.children()[0].className).toContain('my-class');
});
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
$compileProvider.directive('test', function() {
return {
templateUrl: 'test.html'
};
});
$httpBackend.whenGET('test.html').respond('hello');
element.append('<div ng-if="show" test></div>');
$compile(element)($rootScope);
$rootScope.show = true;
$rootScope.$apply();
expect(element.text()).toBe('');
$httpBackend.flush();
expect(element.text()).toBe('hello');
$rootScope.show = false;
$rootScope.$apply();
// Note: there are still comments in element!
expect(element.children().length).toBe(0);
expect(element.text()).toBe('');
}));
});
describe('ngIf and transcludes', function() {
+44 -4
View File
@@ -47,7 +47,7 @@ describe('ngInclude', function() {
$rootScope.url = 'myUrl';
$rootScope.$digest();
expect(body.text()).toEqual('misko');
body.html('');
body.empty();
}));
@@ -60,7 +60,7 @@ describe('ngInclude', function() {
$rootScope.url = 'myUrl';
$rootScope.$digest();
expect(element.text()).toEqual('Alibaba');
jqLite(document.body).html('');
jqLite(document.body).empty();
}));
@@ -74,7 +74,7 @@ describe('ngInclude', function() {
expect(function() { $rootScope.$digest(); }).toThrowMinErr(
'$sce', 'insecurl',
/Blocked loading resource from url not allowed by \$sceDelegate policy. URL: http:\/\/example.com\/myUrl.*/);
jqLite(document.body).html('');
jqLite(document.body).empty();
}));
@@ -88,7 +88,7 @@ describe('ngInclude', function() {
expect(function() { $rootScope.$digest(); }).toThrowMinErr(
'$sce', 'insecurl',
/Blocked loading resource from url not allowed by \$sceDelegate policy. URL: http:\/\/example.com\/myUrl.*/);
jqLite(document.body).html('');
jqLite(document.body).empty();
}));
@@ -524,6 +524,46 @@ describe('ngInclude and transcludes', function() {
});
});
it('should link directives on the same element after the content has been loaded', function() {
var contentOnLink;
module(function() {
directive('test', function() {
return {
link: function(scope, element) {
contentOnLink = element.text();
}
};
});
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.expectGET('include.html').respond('someContent');
element = $compile('<div><div ng-include="\'include.html\'" test></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(contentOnLink).toBe('someContent');
});
});
it('should add the content to the element before compiling it', function() {
var root;
module(function() {
directive('test', function() {
return {
link: function(scope, element) {
root = element.parent().parent();
}
};
});
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.expectGET('include.html').respond('<span test></span>');
element = $compile('<div><div ng-include="\'include.html\'"></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(root[0]).toBe(element[0]);
});
});
});
describe('ngInclude animations', function() {
+26
View File
@@ -13,4 +13,30 @@ describe('ngInit', function() {
element = $compile('<div ng-init="a=123"></div>')($rootScope);
expect($rootScope.a).toEqual(123);
}));
it("should be evaluated before ngInclude", inject(function($rootScope, $templateCache, $compile) {
$templateCache.put('template1.tpl', '<span>1</span>');
$templateCache.put('template2.tpl', '<span>2</span>');
$rootScope.template = 'template1.tpl';
element = $compile('<div><div ng-include="template" ' +
'ng-init="template=\'template2.tpl\'"></div></div>')($rootScope);
$rootScope.$digest();
expect($rootScope.template).toEqual('template2.tpl');
expect(element.find('span').text()).toEqual('2');
}));
it("should be evaluated after ngController", function() {
module(function($controllerProvider) {
$controllerProvider.register('TestCtrl', function($scope) {});
});
inject(function($rootScope, $compile) {
element = $compile('<div><div ng-controller="TestCtrl" ' +
'ng-init="test=123"></div></div>')($rootScope);
$rootScope.$digest();
expect($rootScope.test).toBeUndefined();
expect(element.children('div').scope().test).toEqual(123);
});
});
});
+23 -1
View File
@@ -749,8 +749,30 @@ describe('ngRepeat', function() {
expect(element.text()).toBe('123');
}));
}
});
it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) {
$compileProvider.directive('test', function() {
return {
templateUrl: 'test.html'
};
});
$httpBackend.whenGET('test.html').respond('hello');
element = jqLite('<div><div ng-repeat="i in items" test></div></div>');
$compile(element)($rootScope);
$rootScope.items = [1];
$rootScope.$apply();
expect(element.text()).toBe('');
$httpBackend.flush();
expect(element.text()).toBe('hello');
$rootScope.items = [];
$rootScope.$apply();
// Note: there are still comments in element!
expect(element.children().length).toBe(0);
expect(element.text()).toBe('');
}));
});
it('should add separator comments after each item', inject(function ($compile, $rootScope) {
var check = function () {
+52 -4
View File
@@ -436,13 +436,61 @@ describe('$httpBackend', function() {
it('should convert 0 to 404 if no content - relative url', function() {
$backend = createHttpBackend($browser, MockXhr, null, null, null, 'file');
var originalUrlParsingNode = urlParsingNode;
$backend('GET', '/whatever/index.html', null, callback);
respond(0, '');
//temporarily overriding the DOM element to pretend that the test runs origin with file:// protocol
urlParsingNode = {
hash : "#/C:/",
host : "",
hostname : "",
href : "file:///C:/base#!/C:/foo",
pathname : "/C:/foo",
port : "",
protocol : "file:",
search : "",
setAttribute: angular.noop
};
try {
$backend = createHttpBackend($browser, MockXhr);
$backend('GET', '/whatever/index.html', null, callback);
respond(0, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(404);
} finally {
urlParsingNode = originalUrlParsingNode;
}
});
it('should return original backend status code if different from 0', function () {
$backend = createHttpBackend($browser, MockXhr);
// request to http://
$backend('POST', 'http://rest_api/create_whatever', null, callback);
respond(201, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(404);
expect(callback.mostRecentCall.args[0]).toBe(201);
// request to file://
$backend('POST', 'file://rest_api/create_whatever', null, callback);
respond(201, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(201);
// request to file:// with HTTP status >= 300
$backend('POST', 'file://rest_api/create_whatever', null, callback);
respond(503, '');
expect(callback).toHaveBeenCalled();
expect(callback.mostRecentCall.args[0]).toBe(503);
});
});
});
+1 -2
View File
@@ -38,7 +38,6 @@ describe('$location', function() {
if ($sniffer.msie) return;
//reset urlParsingNode
urlParsingNode = urlParsingNodePlaceholder;
expect(urlParsingNode.pathname).not.toBe('/C:/foo');
}));
@@ -324,7 +323,7 @@ describe('$location', function() {
});
it('should parse hashband url into path and search', function() {
it('should parse hashbang url into path and search', function() {
expect(url.protocol()).toBe('http');
expect(url.host()).toBe('www.server.org');
expect(url.port()).toBe(1234);
+90 -1
View File
@@ -127,6 +127,15 @@ describe('Scope', function() {
});
});
it('should clear phase if an exception interrupt $digest cycle', function() {
inject(function($rootScope) {
$rootScope.$watch('a', function() {throw new Error('abc');});
$rootScope.a = 1;
try { $rootScope.$digest(); } catch(e) { }
expect($rootScope.$$phase).toBeNull();
});
});
it('should fire watches in order of addition', inject(function($rootScope) {
// this is not an external guarantee, just our own sanity
@@ -568,6 +577,82 @@ describe('Scope', function() {
});
});
});
describe('optimizations', function() {
function setupWatches(scope, log) {
scope.$watch(function() { log('w1'); return scope.w1; }, log.fn('w1action'));
scope.$watch(function() { log('w2'); return scope.w2; }, log.fn('w2action'));
scope.$watch(function() { log('w3'); return scope.w3; }, log.fn('w3action'));
scope.$digest();
log.reset();
}
it('should check watches only once during an empty digest', inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$digest();
expect(log).toEqual(['w1', 'w2', 'w3']);
}));
it('should quit digest early after we check the last watch that was previously dirty',
inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.w1 = 'x';
$rootScope.$digest();
expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w1']);
}));
it('should not quit digest early if a new watch was added from an existing watch action',
inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$watch(log.fn('w4'), function() {
log('w4action');
$rootScope.$watch(log.fn('w5'), log.fn('w5action'));
});
$rootScope.$digest();
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action',
'w1', 'w2', 'w3', 'w4', 'w5', 'w5action',
'w1', 'w2', 'w3', 'w4', 'w5']);
}));
it('should not quit digest early if an evalAsync task was scheduled from a watch action',
inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$watch(log.fn('w4'), function() {
log('w4action');
$rootScope.$evalAsync(function() {
log('evalAsync')
});
});
$rootScope.$digest();
expect(log).toEqual(['w1', 'w2', 'w3', 'w4', 'w4action', 'evalAsync',
'w1', 'w2', 'w3', 'w4']);
}));
it('should quit digest early but not too early when various watches fire', inject(function(log, $rootScope) {
setupWatches($rootScope, log);
$rootScope.$watch(function() { log('w4'); return $rootScope.w4; }, function(newVal) {
log('w4action');
$rootScope.w2 = newVal;
});
$rootScope.$digest();
log.reset();
$rootScope.w1 = 'x';
$rootScope.w4 = 'x';
$rootScope.$digest();
expect(log).toEqual(['w1', 'w1action', 'w2', 'w3', 'w4', 'w4action',
'w1', 'w2', 'w2action', 'w3', 'w4',
'w1', 'w2']);
}));
});
});
@@ -590,10 +675,14 @@ describe('Scope', function() {
}));
it('should ignore remove on root', inject(function($rootScope) {
it('should broadcast $destroy on rootScope', inject(function($rootScope) {
var spy = spyOn(angular, 'noop');
$rootScope.$on('$destroy', angular.noop);
$rootScope.$destroy();
$rootScope.$digest();
expect(log).toEqual('123');
expect(spy).toHaveBeenCalled();
expect($rootScope.$$destroyed).toBe(true);
}));
+230
View File
@@ -0,0 +1,230 @@
'use strict';
describe('sanitizeUri', function() {
var sanitizeHref, sanitizeImg, sanitizeUriProvider, testUrl;
beforeEach(function() {
module(function(_$$sanitizeUriProvider_) {
sanitizeUriProvider = _$$sanitizeUriProvider_;
});
inject(function($$sanitizeUri) {
sanitizeHref = function(uri) {
return $$sanitizeUri(uri, false);
};
sanitizeImg = function(uri) {
return $$sanitizeUri(uri, true);
}
});
});
function isEvilInCurrentBrowser(uri) {
var a = document.createElement('a');
a.setAttribute('href', uri);
return a.href.substring(0, 4) !== 'http';
}
describe('img[src] sanitization', function() {
it('should sanitize javascript: urls', function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
});
it('should sanitize non-image data: urls', function() {
testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
expect(sanitizeImg(testUrl)).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
testUrl = "data:,foo";
expect(sanitizeImg(testUrl)).toBe("unsafe:data:,foo");
});
it('should not sanitize data: URIs for images', function() {
// image data uri
// ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
testUrl = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
expect(sanitizeImg(testUrl)).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
});
it('should sanitize mailto: urls', function() {
testUrl = "mailto:foo@bar.com";
expect(sanitizeImg(testUrl)).toBe('unsafe:mailto:foo@bar.com');
});
it('should sanitize obfuscated javascript: urls', function() {
// case-sensitive
testUrl = "JaVaScRiPt:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
testUrl = "java\u0009script:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// space before
testUrl = " javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
testUrl = " \u000e javascript:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// post-fixed with proper url
testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
expect(sanitizeImg(testUrl)).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
});
it('should sanitize ng-src bindings as well', function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
});
it('should not sanitize valid urls', function() {
testUrl = "foo/bar";
expect(sanitizeImg(testUrl)).toBe('foo/bar');
testUrl = "/foo/bar";
expect(sanitizeImg(testUrl)).toBe('/foo/bar');
testUrl = "../foo/bar";
expect(sanitizeImg(testUrl)).toBe('../foo/bar');
testUrl = "#foo";
expect(sanitizeImg(testUrl)).toBe('#foo');
testUrl = "http://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe('http://foo.com/bar');
testUrl = " http://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe(' http://foo.com/bar');
testUrl = "https://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe('https://foo.com/bar');
testUrl = "ftp://foo.com/bar";
expect(sanitizeImg(testUrl)).toBe('ftp://foo.com/bar');
testUrl = "file:///foo/bar.html";
expect(sanitizeImg(testUrl)).toBe('file:///foo/bar.html');
});
it('should allow reconfiguration of the src whitelist', function() {
var returnVal;
expect(sanitizeUriProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
returnVal = sanitizeUriProvider.imgSrcSanitizationWhitelist(/javascript:/);
expect(returnVal).toBe(sanitizeUriProvider);
testUrl = "javascript:doEvilStuff()";
expect(sanitizeImg(testUrl)).toBe('javascript:doEvilStuff()');
testUrl = "http://recon/figured";
expect(sanitizeImg(testUrl)).toBe('unsafe:http://recon/figured');
});
});
describe('a[href] sanitization', function() {
it('should sanitize javascript: urls', inject(function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should sanitize data: urls', inject(function() {
testUrl = "data:evilPayload";
expect(sanitizeHref(testUrl)).toBe('unsafe:data:evilPayload');
}));
it('should sanitize obfuscated javascript: urls', inject(function() {
// case-sensitive
testUrl = "JaVaScRiPt:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// tab in protocol
testUrl = "java\u0009script:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// space before
testUrl = " javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
// ws chars before
testUrl = " \u000e javascript:doEvilStuff()";
if (isEvilInCurrentBrowser(testUrl)) {
expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()');
}
// post-fixed with proper url
testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
expect(sanitizeHref(testUrl)).toBeOneOf(
'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
);
}));
it('should sanitize ngHref bindings as well', inject(function() {
testUrl = "javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()');
}));
it('should not sanitize valid urls', inject(function() {
testUrl = "foo/bar";
expect(sanitizeHref(testUrl)).toBe('foo/bar');
testUrl = "/foo/bar";
expect(sanitizeHref(testUrl)).toBe('/foo/bar');
testUrl = "../foo/bar";
expect(sanitizeHref(testUrl)).toBe('../foo/bar');
testUrl = "#foo";
expect(sanitizeHref(testUrl)).toBe('#foo');
testUrl = "http://foo/bar";
expect(sanitizeHref(testUrl)).toBe('http://foo/bar');
testUrl = " http://foo/bar";
expect(sanitizeHref(testUrl)).toBe(' http://foo/bar');
testUrl = "https://foo/bar";
expect(sanitizeHref(testUrl)).toBe('https://foo/bar');
testUrl = "ftp://foo/bar";
expect(sanitizeHref(testUrl)).toBe('ftp://foo/bar');
testUrl = "mailto:foo@bar.com";
expect(sanitizeHref(testUrl)).toBe('mailto:foo@bar.com');
testUrl = "file:///foo/bar.html";
expect(sanitizeHref(testUrl)).toBe('file:///foo/bar.html');
}));
it('should allow reconfiguration of the href whitelist', function() {
var returnVal;
expect(sanitizeUriProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
returnVal = sanitizeUriProvider.aHrefSanitizationWhitelist(/javascript:/);
expect(returnVal).toBe(sanitizeUriProvider);
testUrl = "javascript:doEvilStuff()";
expect(sanitizeHref(testUrl)).toBe('javascript:doEvilStuff()');
testUrl = "http://recon/figured";
expect(sanitizeHref(testUrl)).toBe('unsafe:http://recon/figured');
});
});
});
+8 -1
View File
@@ -1,7 +1,7 @@
'use strict';
describe('urlUtils', function() {
describe('parse', function() {
describe('urlResolve', function() {
it('should normalize a relative url', function () {
expect(urlResolve("foo").href).toMatch(/^https?:\/\/[^/]+\/foo$/);
});
@@ -14,6 +14,13 @@ describe('urlUtils', function() {
expect(parsed.hostname).not.toBe("");
expect(parsed.pathname).not.toBe("");
});
it('should return pathname as / if empty path provided', function () {
//IE counts / as empty, necessary to use / so that pathname is not context.html
var parsed = urlResolve('/');
expect(parsed.pathname).toBe('/');
})
});
describe('isSameOrigin', function() {
+2636 -2480
View File
File diff suppressed because it is too large Load Diff

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