Compare commits

..

112 Commits

Author SHA1 Message Date
Julie Ralph 0524e92d2e docs(migration): add end to end upgrade info to migration doc
There are a couple of changes to some Protractor tests that need to be made
when migrating from AngularJS 1.2 to 1.3 - document these in the migration
guide.

See https://github.com/angular/protractor/issues/1480

Closes #10377
2014-12-15 13:46:21 +00:00
Shahar Talmi 9b96cea462 feat($rootScope): allow passing locals argument to $evalAsync
Closes #10390
2014-12-15 13:45:12 +00:00
Jason Bedard c90ad96808 fix($parse): a chain of field accessors should use a single getterFn 2014-12-15 13:03:05 +01:00
Lucas Galfaso 69f69db1e0 revert($compile): use createMap() for $$observe listeners when initialized from attr interpolation
This reverts commit 8e28bb4c2f.
2014-12-14 13:32:55 +01:00
Jason Bedard 8e28bb4c2f fix($compile): use createMap() for $$observe listeners when initialized from attr interpolation 2014-12-14 12:41:04 +01:00
Pawel Kozlowski ab41e48493 docs(dateFilter): fix docs to match implementation for week no formatting
The existing documentation claims that dateFilter determines week no
according to the ISO8601 standard, but this is not the case as illustrated
by tests in this PR. More specifically, the implementation deviates from
ISO8601 in 2 important aspects:
- impl assumes Sun to be the first day of a week, ISO8601 mandates Mon
- impl allows weeks 0 (for years starting on Fri, Sat) while ISO8601
would mark them as a week 52/53 of a previous year.

Fixes #10314
Closes #10313

Closes #10445
2014-12-14 11:28:46 +01:00
Sekib Omazic ef640cbc2a fix(ngRepeat): allow extra whitespaces in (key,value) part of micro-syntax
e.g. (  aaa  ,   bbb  ) will be accepted by parser

Fixes #6827
Closes #6833
2014-12-14 10:22:38 +01:00
Ruben Vicario Gonzalez 081fef60b2 docs(tutorial/index): improve punctuation
Closes #10449
2014-12-14 09:50:28 +01:00
Wes 99d1a438b6 docs(Courses): fix syntax issue in developer guide
The courses section should use commas between links to differentiate the instances of each link

Closes #10448
2014-12-13 21:27:18 -05:00
Dan Tennery-Spalding 6bd4292c24 docs(API Reference): corrected two typos - two missing commas
In the ngAnimate section, there were two commas missing from two sentences. This is inconsistent with the grammar used in the rest of the API documentation and made the document (slightly) more difficult to read. The two sentences are shown below, with the new commas added:

1. "Once defined, the animation can be triggered"
                           ^
                    comma added

2. "Once registered, the animation can be triggered"
                              ^
                    comma added

Closes #10447
2014-12-13 20:17:54 -05:00
Aleksandar Djindjic e0663312fb docs($rootScope): clean up inheritance example
Closes #10441
2014-12-13 18:06:50 +01:00
Jason Bedard 9ae0c01c2b perf($compile): only re-$interpolate attribute values at link time if changed since compile 2014-12-13 12:56:39 +01:00
Jon Hoguet 79b3b8b686 chore($location): use $window instead of window
Fix one place were there was a reference to `window` and not to `$window`
2014-12-13 12:52:42 +01:00
Pawel Kozlowski 1b740974f5 feat($http): pass response status code to data transform functions
Fixes #10324
Closes #6734

Closes #10440
2014-12-12 19:51:34 +01:00
Georgios Kalpakas b9bdbe615c fix($http): only parse as JSON when opening/closing brackets match
Previously, due to weak JSON-detecting RegExp, string like `[...}` and
`{...]` would be considered JSON (even if they obviously aren't) and an
expection would be thrown while trying to parse them.

This commit makes sure the opening and closing brackets match. This
doesn't completely eliminate false positives (e.g. `[]{}[]`), but does
help reduce them.

Closes #10349
Closes #10357
2014-12-12 18:46:00 +01:00
Stefan 6617b42bc7 docs(guide/Forms): added link to input type "date"
Closes #10415
2014-12-11 21:19:32 +01:00
Vojta Jina 8a907eb3ff chore(travis): run the build twice (BS and SL)
Temporarily run the each job twice:
- using BrowserStack
- using SauceLabs
2014-12-11 09:52:00 -08:00
Rouven Weßling aac3c4aa63 chore($$raf) remove moz prefix for requestAnimationFrame
This drops support for Firefox 22 and older.

The moz-prefix is still supported, but there is an effort to drop it eventually:
https://bugzilla.mozilla.org/show_bug.cgi?id=909154

Closes #9577
2014-12-10 16:01:02 -05:00
Pawel Kozlowski d162f152b8 refactor($http): avoid re-creating execHeaders function
The execHeaders function was being re-defined inside mergeHeaders
function. Additionally it was mutating its arguments.

Closes #10359
2014-12-10 20:57:28 +01:00
Rouven Weßling 4025883803 fix($http): don't convert FormData objects to JSON
This won't enable FormData uploads in itself, as the Content-Type is automatically set to application/json.

Closes #10373
2014-12-10 19:06:07 +01:00
thorn0 c437d0a470 docs(CHANGELOG.md): better group changes in 1.3.6
Closes #10392
2014-12-10 19:04:00 +01:00
Danny Callaghan 5d28d19623 docs(guide/Animations): fix punctuation
Closes #10398
2014-12-10 19:01:42 +01:00
Caitlin Potter 7fd2dc11ca chore(bower/unpublish.sh): add angular-messages and angular-aria
The unpublish script was not set to unpublish those packages

Closes #10379
2014-12-09 16:20:06 -05:00
Julie Ralph 63db09753e style(testability): throw a more informative error when getting testability
The angular.getTestability method requires an element parameter to determine
which Angular application to use. Currently, if the element provided is
undefined or outside of an Angular app, the error message is 'cannot read
property get of undefined'. Improving to a more relevant error message.
2014-12-09 11:48:38 -08:00
Caitlin Potter a097aa95b7 fix(orderBy): do not try to call valueOf/toString on null
8bfeddb5d6 added changes to make relational operator work as it
normally would in JS --- unfortunately, this broke due to my failure to account for typeof null
being "object".

This refactoring attempts to convert object values to primitives still, in a fashion similar to
the SortCompare (and subsequently the ToString() algorithm) from ES, in order to account for `null`
and also simplify code to some degree.

BREAKING CHANGE:

Previously, if either value being compared in the orderBy comparator was null or undefined, the
order would not change. Now, this order behaves more like Array.prototype.sort, which by default
pushes `null` behind objects, due to `n` occurring after `[` (the first characters of their
stringified forms) in ASCII / Unicode. If `toString` is customized, or does not exist, the
behaviour is undefined.

Closes #10385
Closes #10386
2014-12-09 13:34:27 -05:00
Pawel Kozlowski b3dfb38359 refactor($http): avoid using closure vars in serverRequest fn
Closes #10361
2014-12-09 19:24:52 +01:00
Pawel Kozlowski 3b5ba87797 refactor($http): drop superfluous argument to sendReq
Closes #10360
2014-12-09 18:50:17 +01:00
Grzegorz Marzencki e50002baed docs($resource): fix typo
Closes #10383
2014-12-09 18:12:22 +01:00
Brian Scoles a8089166f5 docs(guide/Expressions): fix grammar, flow and punctuation
Closes #10384
2014-12-09 18:10:08 +01:00
Wesley Cho d8e3707860 feat($compile): add support for ng-attr with camelCased attributes
SVG attributes are case sensitive and some have upper case letters in them
This change ensures that we can identify these, when being used with the `ng-attr`
directive, by encoding upper case letters with a preceding underscore.

For example to apply `ng-attr` to the `viewBox` attribute we could write
`ng-attr-view_box` - or any of the other variants: `ng:attr:view_box`,
`data-ng-attr-view_box`, etc.

Closes #9845
Closes #10194
2014-12-09 11:34:27 +00:00
Caitlin Potter ca4df475e1 docs(CHANGELOG.md): remove the closes ... from 2dc34a96 notes 2014-12-08 19:12:15 -05:00
Caitlin Potter 02c9dc6e16 docs(CHANGELOG): add v1.3.6 changes
Closes #10376
2014-12-08 19:10:34 -05:00
Tobias Davis 6ad109e745 docs($interval): correcting example code indentation
Bueno!

Closes #10372
2014-12-08 16:29:39 -05:00
Georgios Kalpakas c5cba6e9c1 chore(changelog): add test for addition of trailing newline
Adds tests for the functionality added by #9550.

Closes #10358
2014-12-08 13:04:15 -05:00
active-low 924d3c6bfe docs(contributing): correct push -f command
`git push -f` needs branch specification

In all cases, please consult the git manpages before consulting angular's contributing guide
when you need help with git, thx ^^

Closes #10356
2014-12-08 11:05:49 -05:00
Giuseppe Caruso ee29819dba docs(guide/controllers): Just a typo
Gingerbreak would break testing. :)

Oh my gosh he's right, it totally w/ould. That is so embarrassing!

Closes #10353
2014-12-08 10:50:03 -05:00
Clark DuVall 41f03e4b02 docs(ngView): remove multiple position: relative
Closes #10363
2014-12-08 09:04:13 +01:00
Shahar Talmi facfec9841 fix(http): preserve config object when resolving from cache
Fixes #9004
Closes #9030
2014-12-06 10:16:28 +01:00
Vojta Jina e2d1969d68 chore(grunt): remove unused code 2014-12-05 19:13:24 -08:00
Vojta Jina f380cd220c chore(travis): clean up browserstack/saucelabs scripts 2014-12-05 19:13:24 -08:00
Vojta Jina 2db0aabee3 chore(travis): enable both SauceLabs and BrowserStack
Setting env var `BROWSER_PROVIDER` to `browserstack` or `saucelabs`
determines which browser provider will be used.

This does not affect the build as all jobs are set to use SauceLabs.

Switch to Karma with Socket.io 1.x, which solves some issues(*) with BS.
Thus removing `config.transports` as it is not used anymore
(Socket.io 1.x starts with polling and tries to upgrade if available).

(*) folks from BS were fiddling with socket.io configuration to get it stable.
See https://github.com/dhimil/karma/commit/4c04011850bf66a8a7556cd76ad662c568399481
This is not necessary with Socket.io 1.x.
2014-12-05 19:13:23 -08:00
Ciro Nunes ee42cfea04 refactor(ngAria): remove local camelCase method
Closes #10338
Closes #10337
2014-12-05 22:13:24 +00:00
Ciro Nunes fbbf6ac161 docs(compile): document $attrs.$normalize
Closes #10345
2014-12-05 14:18:56 -05:00
Clay Anderson a1c5f2b4f0 docs(guide/forms): enhanced form examples to utilize $touched
The "Binding to form and control state" example now makes use of
control states that were introduced in 1.3.
For example, users are now informed of validation requirements upon
clicking 'Save'.

Closes #10066
2014-12-05 15:28:21 +01:00
Martin Staffa 2d6a0a1dc1 fix(ngModelController): always use the most recent viewValue for validation
This fixes issues where a parser calls $setViewValue. This is a common
strategy for manipulating the $viewValue while the user is entering
data into an input field.

When the $viewValue was changed inside the parser, the new viewValue
would be committed, parsed and used for validation. The original parser
however would run after that and pass the original (outdated) viewValue
on to the validators, which could cause false positives, e.g. for
minlength.

Fixes #10126
Fixes #10299
2014-12-05 14:36:41 +01:00
Mads Konradsen 0c4f9fa574 docs(ie): remove fixes needed for IE8 since we don't support it now
Closes #10330
Closes #10316
2014-12-05 12:20:34 +00:00
Peter Bacon Darwin 7ad66527ba docs(guide/migration): add info about change that could break isolated directive usage
Closes #10236
2014-12-05 11:32:25 +00:00
Caitlin Potter f2e7f875e2 feat($$jqLite): export jqLite as a private service
This makes it easy to use jqLite's nicer class API (compared to jQuery) in modules
like ngAnimate.
2014-12-04 18:05:07 -05:00
Caitlin Potter 40a537c25f fix(ngAnimate): do not use jQuery class API
jQeury's class API causes problems with SVG elements --- using jqLite in all cases
prevents issues.

Closes #10024
Closes #10329
2014-12-04 18:05:00 -05:00
Brian Ford cb192293f4 docs($animate): improve formatting of inline code 2014-12-04 13:15:09 -08:00
Pawel Kozlowski 8c3a42cd68 refactor($templateRequest): avoid double calls to $templateCache.put
Closes #10265
2014-12-04 21:24:06 +01:00
Peter Bacon Darwin 96e7897fef docs($location.ihshprfx): remove docs for unused error 2014-12-04 12:52:43 +00:00
Peter Bacon Darwin 2dc34a9699 fix($location): allow hash fragments with hashPrefix in hash-bang location urls
Previously if there was a hash fragment but no hashPrefix we would throw an error.
Now we assume that the hash-bang path is empty and that the hash is a valid fragment.

This prevents unnecessary exceptions where we clear the hashBang path, say by
navigating back to the base url, where the $browser leaves an empty hash symbol
on the URL to ensure there is no browser reload.

BREAKING CHANGE:

We no longer throw an `ihshprfx` error if the URL after the base path
contains only a hash fragment.  Previously, if the base URL was `http://abc.com/base/`
and the hashPrefix is `!` then trying to parse `http://abc.com/base/#some-fragment`
would have thrown an error. Now we simply assume it is a normal fragment and
that the path is empty, resulting `$location.absUrl() === "http://abc.com/base/#!/#some-fragment"`.

This should not break any applications, but you can no longer rely on receiving the
`ihshprfx` error for paths that have the syntax above. It is actually more similar
to what currently happens for invalid extra paths anyway:  If the base URL
and hashPrfix are set up as above, then `http://abc.com/base/other/path` does not
throw an error but just ignores the extra path: `http://abc.com/base`.

Closes #9629
Closes #9635
Closes #10228
Closes #10308
2014-12-04 12:49:31 +00:00
Peter Bacon Darwin 10ac594809 fix($browser): prevent infinite digests when clearing the hash of a url
By using `location.hash` to update the current browser location when only
the hash has changed, we prevent the browser from attempting to reload.

Closes #9629
Closes #9635
Closes #10228
Closes #10308
2014-12-04 12:49:31 +00:00
Jason Bedard d21dff21ed fix(ngmodel): fixing many keys incorrectly marking inputs as dirty 2014-12-03 16:47:35 -05:00
Jason Bedard 55d9db56a6 fix(inputs): ignoring input events in IE caused by placeholder changes or focus/blur on inputs with placeholders
Closes #9265
2014-12-03 16:46:55 -05:00
Lucas Galfaso 579aa59324 chore(docs): fix jscs error
Removed a triling whitespace
2014-12-03 21:45:47 +01:00
Steve Shaffer b9e5eaf669 docs(select): Updated ngOptions track by examples
Made the example shown consistent with the advice above it regarding not using
`select as` and `track by` in the same comprehension expression.  Also changed
references to `trackexpr` to `track by` since `trackexpr` is not defined
except in the examples.

Added filter + track by example for ngOptions

The documentation for ngRepeat includes such an example specifying the proper
order for filters and and "track by" clauses in the comprehension expression,
but these docs for ngOptions do not.
2014-12-03 21:31:43 +01:00
Justin 228281eecc docs($location): improve $location.hash() example
Closes #10300
2014-12-03 21:11:27 +01:00
Pawel Kozlowski acb066e84a fix(ngMock): allow numeric timeouts in $httpBackend mock
Fixes #4891
2014-12-03 21:06:33 +01:00
Lucas Galfaso ed1243ffc7 fix(parse): fix operators associativity
Make the operators `&&`, `==`, `!=`, `===`, `!==`, `<`, `>`, `<=`, `>=`
follow Javascript left-to-right associativity
2014-12-03 20:53:20 +01:00
Caitlin Potter 3aa5752894 fix(orderBy): make object-to-primtiive behaviour work for objects with null prototype 2014-12-03 14:19:25 -05:00
Caitlin Potter 8bfeddb5d6 fix(orderBy): maintain order in array of objects when predicate is not provided
In ES262, there are two properties which are used to get a primitive value from an Object:

- valueOf() -- a method which returns a primitive value represented by the Object
- toString() -- a method which returns a string value representing the Object.

When comparing objects using relational operators, the abstract operation ToPrimitive(O, TypeHint) is used,
which will use these methods to retrieve a value.

This CL emulates the behaviour of ToPrimitive(), and ensures that no ordering occurs if the retrieved value
is identical.

This behaviour was previously used for Date objects, however it can be safely made generic as it applies to
all objects.

Closes #9566
Closes #9747
Closes #10311
2014-12-03 14:19:24 -05:00
hartz89 015111fd79 docs(ngTransclude): improve markup consistency
Closes #10298
2014-12-03 19:38:00 +01:00
Julien Valéry cc0fbe37d4 docs($compile): fix spelling
Closes #10296
2014-12-03 19:21:38 +01:00
Jason Bedard 1b640f9665 test($interpolate): adding tests for watching $interpolate functions
Closes #10119
2014-12-03 19:15:08 +01:00
Caitlin Potter 32806caf13 docs(guide/css-styling): form controls are not always input elements
People frequently write custom form controls using the `ngModel` directive, this just
refactors the text to be more clear that this is possible (imho).
2014-12-02 22:00:00 -05:00
Caitlin Potter 9c113aa4af style(guide/css-styling): remove trailing whitespace
Pretty self-explanatory, no mystery here.
2014-12-02 22:00:00 -05:00
Caitlin Potter 9a616eade4 refactor(toJson): remove breaking change from previous CL
Closes #10297
2014-12-02 17:32:39 -05:00
Caitlin Potter 7daf4e0125 test(toJson): add extra test cases for new pretty behaviour 2014-12-02 17:32:38 -05:00
Rahul Doshi 1191edba4e feat(jsonFilter): add optional arg to define custom indentation
also change toJson function to accomodate passing in of number of spaces

Closes #9771
2014-12-02 17:32:38 -05:00
Philipp Denzler c8c9bbc412 docs(guide/Working With CSS): ng-touched/untouched
Add description for ng-touched and ng-untouched CSS classes.

Closes #10302
2014-12-02 17:13:34 -05:00
Lucas Galfaso 429938da1f fix($parse): Follow JavaScript context for unbound functions
Use `undefined` as the context when a function is ounbound.
E.g. when executing `foo()()`, then `foo()` is executed using the
scope as the context, the function returned by `foo()` will
have an `undefined` context
2014-12-02 22:45:32 +01:00
Georgios Kalpakas a75537d461 fix(filterFilter): don't match primitive sub-expressions against any prop
Basically, implement the logic detailed in the 2nd point of
https://github.com/angular/angular.js/pull/9757#issuecomment-63544399
2014-12-02 13:52:21 -05:00
Georgios Kalpakas 5ced914cc8 fix(filterFilter): ignore function properties and account for inherited properties
Closes #9984
2014-12-02 13:52:21 -05:00
Georgios Kalpakas a631a759d2 test(filter): test expression object with inherited properties
Related to #9984
2014-12-02 13:52:21 -05:00
Georgios Kalpakas f7cf846045 fix(filterFilter): correctly handle deep expression objects
Previously, trying to use a deep expression object (i.e. an object whose
properties can be objects themselves) did not work correctly.
This commit refactors `filterFilter`, making it simpler and adding support
for filtering collections of arbitrarily deep objects.

Closes #7323
Closes #9698
Closes #9757
2014-12-02 13:52:11 -05:00
Pawel Kozlowski 96c61fe756 fix(numberFilter): numbers rounding to zero shouldn't be negative
Closes #10278
2014-12-02 19:36:39 +01:00
chasefleming 915a891ad4 fix(linky): make urls starting with www. links, like markdown
It's super cool!

Closes #10290
2014-12-02 13:25:54 -05:00
Carson McDonald 8df47db72f docs(guide/scope): fix typo
Closes #10289
2014-12-02 19:21:02 +01:00
Olivier Combe 013b522c9e feat($injector): print caller name in "unknown provider" errors (when available)
NGEUROPE!!!!!

Closes #8135
Closes #9721
2014-12-02 12:19:15 -05:00
Mike Stickel 7c6be43e83 fix(ngSanitize): exclude smart quotes at the end of the link
When smart quotes are included in content filtered through linky, any
smart quote at the end of a URL string was being included in the link
text and the href.

Closes #7307
2014-12-02 12:11:35 +00:00
Peter Bacon Darwin e93710fe0e fix($location): strip off empty hash segments when comparing
The url is the same whether or not there is an empty `#` marker at the end.
This prevents unwanted calls to update the browser, since the browser is
automatically applying an empty hash if necessary to prevent page reloads.

Closes #9635
2014-12-02 12:04:59 +00:00
Marcy Sutton 5481e2cfcd feat(ngAria): bind keypress on ng-click w/ option
Closes #10288
2014-12-01 19:15:57 -05:00
Georgios Kalpakas c6b57f1ec6 docs(guide/accessibility): fix dangling links 2014-12-01 14:43:04 -08:00
Pawel Kozlowski 240e0d5c8e docs(CHANGELOG): add v1.3.5 changes 2014-12-01 22:29:36 +01:00
Martin Staffa 9fa73cb4e7 fix(select): use strict compare when removing option from ctrl
Otherwise, if the removed option was the empty option (value ''),
and the currently selected option had a value of 0, the select
would think that the currently selected option had been removed,
causing the unknown option to be added again.

Fixes #9714
Fixes #10115
Closes #10203
2014-12-01 19:54:14 +01:00
Pawel Kozlowski f6458826ac fix($emplateRequest): propagate rejection reason when ignoreRequestError flag is set
Closes #10266
2014-12-01 19:50:13 +01:00
Pawel Kozlowski ab2531143e refactor($templateRequest): simplify filtering out of transform functions
Closes #10264
2014-12-01 19:31:53 +01:00
Shahar Talmi 9a83f9d2fa fix(ngMock): annotate $RootScopeDecorator
Fixes #10273
Closes #10275
Closes #10277
2014-12-01 19:25:43 +01:00
Peter Bacon Darwin 3f07eb227d test($location): fix test of {rewriteLinks:false}
The test for not rewriting links was invalid and just happened to be
passing by chance (false-positive).
2014-12-01 13:39:27 +00:00
Peter Bacon Darwin 446e5669a1 test($location): fix typo 2014-12-01 13:38:47 +00:00
Peter Bacon Darwin b264be40bc revert: fix(Angular.js): toKeyValue is not serializing null values
This commit contained broken tests and was not ready to be merged.

(reverted from commit 814c9847e8)
2014-12-01 12:42:18 +00:00
Josh Kurz 814c9847e8 fix(Angular.js): toKeyValue is not serializing null values
Signed-off-by: Josh Kurz <jkurz25@gmail.com>
2014-11-30 20:35:14 -05:00
Jesse Palmer dde613f18e chore(docs): fix dangling links warning in $http API
Closes #10270
2014-11-30 12:50:41 +01:00
Chris Tanseer e6a2527cdf docs($rootElement): fix minor grammatical errors
Closes #10269
2014-11-29 23:19:32 +01:00
Caitlin Potter d0351c4803 style(ngHref): make jscs happy ;-; 2014-11-28 22:28:39 -06:00
Sagar Ranglani b2b6d74ae5 docs(ngHref): fix poor paragraph construction
It was bad.

In order to improve the docs, the inclusion of the last sentence would help define `ngHref` well.

Closes #10254
2014-11-28 22:09:18 -06:00
Lucas Galfaso 30694c8027 fix(select): fix several issues when moving options between groups
* When an option was moved to a previous group, the group that
loose the option would remove the label from the controller
* When an entire option group was removed, the options in the
group were mot removed from the controller

Closes #10166
2014-11-27 20:40:43 +00:00
Ates Goral 655ac6474b docs(guide/ie): fixed minor typo
Closes #10251
2014-11-27 21:14:01 +01:00
Eric Theise e5a9b265ba docs(tutorial): fix grammar
Should either be "a different version" or "different versions";
this goes with the latter.

Closes #10249
2014-11-27 19:02:31 +01:00
Danny Shekhtman e2b9eccde0 docs(guide): fix typo
Closes #10245
2014-11-27 08:53:05 +01:00
Sugan Krishnan 9b3d9656a6 docs($compile): fix grammar
Closes #10231
2014-11-27 08:52:13 +01:00
Caitlin Potter 1e6a5b29a6 style(*): IE9 does still have issues with apply on some native functions
This partially reverts 8f05ca5552

Related to #10242
2014-11-26 21:51:31 +00:00
Caitlin Potter 8f05ca5552 style(*): IE is a real browser, and chakra is pretty solid
- IE9+ do not have issues with Function.prototype.bind() on builtin fns (asked Brian Terlson)
  (NOTE: there may still be corner cases where builtins will not have `bind()` --- this may
  need to be reverted on complaint).
- HTMLScriptElement#text is an IDL-spec'd attribute, and we use it in all cases --- so the
  comment was sort of nonsense.
- The value of `msie` does not depend on whether the user is using a "real" browser or not.

Closes #10242
2014-11-26 14:36:33 -06:00
Lucas Galfaso 2ec8d1ffc0 fix(linky): encode all double quotes when serializing email addresses
When encoding a URL or an email address, then escape all double quotes

Closes #10090
2014-11-26 21:35:11 +01:00
Shahar Talmi 08cd5c19c7 fix(ngMock): respond did not always take a statusText argument
minor fix so that `respond` can take a `statusText` argument
even when having status 200 by default

Closes #8270
2014-11-26 20:03:23 +01:00
Or Neeman 41dc7d5ebd docs(ngRoute): clarify JSDoc for caseInsensitiveMatch
Closes #10220
2014-11-26 19:05:53 +01:00
Georgios Kalpakas 0caa5ad83f docs(CHANGELOG): remove * component
Closes #10222
2014-11-26 19:04:24 +01:00
Mathew Foscarini 5d36353bc9 docs($compile): fix grammar
Closes #10204
2014-11-25 19:51:33 +01:00
Guillaume Pannatier 719d5c5fa5 fix($httpBackend): allow canceling request with falsy timeoutId
httpBackend with ngMock browser.defer could never cancel the first deferredFn
because the timeoutId returned by defer for the first fn is a zero value.
Compare timeoutId with undefined fix this issue.

Closes #10177
2014-11-25 19:17:57 +01:00
Guillaume Pannatier 266da34098 test($httpBackend): use browser.defer mock instead of fakeTimeout 2014-11-25 19:17:01 +01:00
90 changed files with 2558 additions and 1054 deletions
+10 -5
View File
@@ -8,14 +8,19 @@ branches:
env:
matrix:
- JOB=unit
- JOB=e2e TEST_TARGET=jqlite
- JOB=e2e TEST_TARGET=jquery
- JOB=unit BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
- JOB=unit BROWSER_PROVIDER=browserstack
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
global:
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
- BROWSER_STACK_USERNAME=VojtaJina
- BROWSER_STACK_ACCESS_KEY=QCQJ1ZpWXpBkSwEdD8ev
- LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
install:
# - npm config set registry http://23.251.144.68
@@ -28,7 +33,7 @@ install:
before_script:
- mkdir -p $LOGS_DIR
- ./lib/sauce/sauce_connect_setup.sh
- ./scripts/travis/start_browser_provider.sh
- npm install -g grunt-cli
- grunt package
- ./scripts/travis/wait_for_browser_provider.sh
+140 -1
View File
@@ -1,3 +1,132 @@
<a name="1.3.6"></a>
# 1.3.6 robofunky-danceblaster (2014-12-08)
## Bug Fixes
- **$browser:** prevent infinite digests when clearing the hash of a url
([10ac5948](https://github.com/angular/angular.js/commit/10ac5948097e2c8eaead238603d29ee580dc8273),
[#9629](https://github.com/angular/angular.js/issues/9629), [#9635](https://github.com/angular/angular.js/issues/9635), [#10228](https://github.com/angular/angular.js/issues/10228), [#10308](https://github.com/angular/angular.js/issues/10308))
- **$http:** preserve config object when resolving from cache
([facfec98](https://github.com/angular/angular.js/commit/facfec98412c0bb8678d578bade05ffef06a9e84),
[#9004](https://github.com/angular/angular.js/issues/9004), [#9030](https://github.com/angular/angular.js/issues/9030))
- **$location:**
- allow hash fragments with hashPrefix in hash-bang location urls
([2dc34a96](https://github.com/angular/angular.js/commit/2dc34a969956eea680be4c8d9f800556d110996a),
[#9629](https://github.com/angular/angular.js/issues/9629), [#9635](https://github.com/angular/angular.js/issues/9635), [#10228](https://github.com/angular/angular.js/issues/10228), [#10308](https://github.com/angular/angular.js/issues/10308))
- strip off empty hash segments when comparing
([e93710fe](https://github.com/angular/angular.js/commit/e93710fe0e4fb05ceee59a04f290692a5bec5d20),
[#9635](https://github.com/angular/angular.js/issues/9635))
- **$parse:**
- fix operators associativity
([ed1243ff](https://github.com/angular/angular.js/commit/ed1243ffc7c2cb4bd5b4dece597597db8eb08e34))
- follow JavaScript context for unbound functions
([429938da](https://github.com/angular/angular.js/commit/429938da1f45b8a649b8c77762fb0ae59b6d0cea))
- **filterFilter:**
- don't match primitive sub-expressions against any prop
([a75537d4](https://github.com/angular/angular.js/commit/a75537d461c92e3455e372ff5005bf0cad2d2e95))
- ignore function properties and account for inherited properties
([5ced914c](https://github.com/angular/angular.js/commit/5ced914cc8625008e6249d5ac5942d5822287cc0),
[#9984](https://github.com/angular/angular.js/issues/9984))
- correctly handle deep expression objects
([f7cf8460](https://github.com/angular/angular.js/commit/f7cf846045b1e2fb39c62e304c61b44d5c805e31),
[#7323](https://github.com/angular/angular.js/issues/7323), [#9698](https://github.com/angular/angular.js/issues/9698), [#9757](https://github.com/angular/angular.js/issues/9757))
- **inputs:** ignoring input events in IE caused by placeholder changes or focus/blur on inputs with placeholders
([55d9db56](https://github.com/angular/angular.js/commit/55d9db56a6f7d29b16f8393612648080c6d535d6),
[#9265](https://github.com/angular/angular.js/issues/9265))
- **linky:** make urls starting with www. links, like markdown
([915a891a](https://github.com/angular/angular.js/commit/915a891ad4cdcaa5e47e976db8f4d402d230be77),
[#10290](https://github.com/angular/angular.js/issues/10290))
- **ngAnimate:** do not use jQuery class API
([40a537c2](https://github.com/angular/angular.js/commit/40a537c25f70ad556a41bb2d00ea3e257410e9af),
[#10024](https://github.com/angular/angular.js/issues/10024), [#10329](https://github.com/angular/angular.js/issues/10329))
- **ngMock:** allow numeric timeouts in $httpBackend mock
([acb066e8](https://github.com/angular/angular.js/commit/acb066e84a10483e1025eed295352b66747dbb8a),
[#4891](https://github.com/angular/angular.js/issues/4891))
- **ngModel:**
- always use the most recent viewValue for validation
([2d6a0a1d](https://github.com/angular/angular.js/commit/2d6a0a1dc1e7125cab2e30244e35e97e11802843),
[#10126](https://github.com/angular/angular.js/issues/10126), [#10299](https://github.com/angular/angular.js/issues/10299))
- fixing many keys incorrectly marking inputs as dirty
([d21dff21](https://github.com/angular/angular.js/commit/d21dff21ed8beb015ad911f11d57cceb56fc439f))
- **ngSanitize:** exclude smart quotes at the end of the link
([7c6be43e](https://github.com/angular/angular.js/commit/7c6be43e83590798cffef63d076fb79d5296fba2),
[#7307](https://github.com/angular/angular.js/issues/7307))
- **numberFilter:** numbers rounding to zero shouldn't be negative
([96c61fe7](https://github.com/angular/angular.js/commit/96c61fe756d7d3db011818bf0925e3d86ffff8ce),
[#10278](https://github.com/angular/angular.js/issues/10278))
- **orderBy:**
- make object-to-primtiive behaviour work for objects with null prototype
([3aa57528](https://github.com/angular/angular.js/commit/3aa5752894419b4638d5c934879258fa6a1c0d07))
- maintain order in array of objects when predicate is not provided
([8bfeddb5](https://github.com/angular/angular.js/commit/8bfeddb5d671017f4a21b8b46334ac816710b143),
[#9566](https://github.com/angular/angular.js/issues/9566), [#9747](https://github.com/angular/angular.js/issues/9747), [#10311](https://github.com/angular/angular.js/issues/10311))
## Features
- **$$jqLite:** export jqLite as a private service
([f2e7f875](https://github.com/angular/angular.js/commit/f2e7f875e2ad4b271c4e72ebd3860f905132eed9))
- **$injector:** print caller name in "unknown provider" errors (when available)
([013b522c](https://github.com/angular/angular.js/commit/013b522c9e690665aecb0e0f656e4557a673ec09),
[#8135](https://github.com/angular/angular.js/issues/8135), [#9721](https://github.com/angular/angular.js/issues/9721))
- **jsonFilter:** add optional arg to define custom indentation
([1191edba](https://github.com/angular/angular.js/commit/1191edba4eaa15f675fa4ed047949a150843971b),
[#9771](https://github.com/angular/angular.js/issues/9771))
- **ngAria:** bind keypress on ng-click w/ option
([5481e2cf](https://github.com/angular/angular.js/commit/5481e2cfcd4d136a1c7f45cd4ce0fa1a8a15074d),
[#10288](https://github.com/angular/angular.js/issues/10288))
## Breaking Changes
- **$location:** due to [2dc34a96](https://github.com/angular/angular.js/commit/2dc34a969956eea680be4c8d9f800556d110996a),
We no longer throw an `ihshprfx` error if the URL after the base path
contains only a hash fragment. Previously, if the base URL was `http://abc.com/base/`
and the hashPrefix is `!` then trying to parse `http://abc.com/base/#some-fragment`
would have thrown an error. Now we simply assume it is a normal fragment and
that the path is empty, resulting `$location.absUrl() === "http://abc.com/base/#!/#some-fragment"`.
This should not break any applications, but you can no longer rely on receiving the
`ihshprfx` error for paths that have the syntax above. It is actually more similar
to what currently happens for invalid extra paths anyway: If the base URL
and hashPrfix are set up as above, then `http://abc.com/base/other/path` does not
throw an error but just ignores the extra path: `http://abc.com/base`.
<a name="1.3.5"></a>
# 1.3.5 cybernetic-mercantilism (2014-12-01)
## Bug Fixes
- **$templateRequest:** propagate rejection reason when ignoreRequestError flag is set
([f6458826](https://github.com/angular/angular.js/commit/f6458826ac974914597a10b0ffdeee3c5d2c62ef),
[#10266](https://github.com/angular/angular.js/issues/10266))
- **$httpBackend:** allow canceling request with falsy timeoutId
([719d5c5f](https://github.com/angular/angular.js/commit/719d5c5fa59ae1617691a0dca02da861fcf5f933),
[#10177](https://github.com/angular/angular.js/issues/10177))
- **linky:** encode all double quotes when serializing email addresses
([2ec8d1ff](https://github.com/angular/angular.js/commit/2ec8d1ffc04e06a39cb1b74a8d675da38e0a1c6b),
[#10090](https://github.com/angular/angular.js/issues/10090))
- **ngMock:**
- annotate $RootScopeDecorator
([9a83f9d2](https://github.com/angular/angular.js/commit/9a83f9d2fabe0a259c283b7f7cd935e4b36e2b5d),
[#10273](https://github.com/angular/angular.js/issues/10273), [#10275](https://github.com/angular/angular.js/issues/10275), [#10277](https://github.com/angular/angular.js/issues/10277))
- respond did not always take a statusText argument
([08cd5c19](https://github.com/angular/angular.js/commit/08cd5c19c7a5116e7e74691391fc5e28bfae4521),
[#8270](https://github.com/angular/angular.js/issues/8270))
- **select:**
- use strict compare when removing option from ctrl
([9fa73cb4](https://github.com/angular/angular.js/commit/9fa73cb4e7190b4d00b65f2f8f9f7d37607308ba),
[#9714](https://github.com/angular/angular.js/issues/9714), [#10115](https://github.com/angular/angular.js/issues/10115), [#10203](https://github.com/angular/angular.js/issues/10203))
- fix several issues when moving options between groups
([30694c80](https://github.com/angular/angular.js/commit/30694c802763d46d6787f7298f47dfef53ed4229),
[#10166](https://github.com/angular/angular.js/issues/10166))
<a name="1.3.4"></a>
# 1.3.4 highfalutin-petroglyph (2014-11-24)
@@ -60,7 +189,7 @@
## Performance Improvements
- ***:** use Object.create instead of creating temporary constructors
- use Object.create instead of creating temporary constructors
([bf6a79c3](https://github.com/angular/angular.js/commit/bf6a79c3484f474c300b5442ae73483030ef5782),
[#10058](https://github.com/angular/angular.js/issues/10058))
@@ -2767,6 +2896,16 @@ jQuery. We don't expect that app code actually depends on this accidental featur
## Breaking Changes
- **$compile:** due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
The isolated scope of a component directive no longer leaks into the template
that contains the instance of the directive. This means that you can no longer
access the isolated scope from attributes on the element where the isolated
directive is defined.
See https://github.com/angular/angular.js/issues/10236 for an example.
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
If you expected `$resource` to strip these types of properties before,
+1 -1
View File
@@ -127,7 +127,7 @@ Before you submit your pull request consider the following guidelines:
```shell
git rebase master -i
git push -f
git push origin my-fix-branch -f
```
That's it! Thank you for your contribution!
-8
View File
@@ -30,14 +30,6 @@ module.exports = function(grunt) {
benchmarksPath: 'benchmarks'
}
},
parallel: {
travis: {
tasks: [
util.parallelTask(['test:unit', 'test:promises-aplus', 'tests:docs'], {stream: true}),
util.parallelTask(['test:e2e'])
]
}
},
connect: {
devserver: {
+7
View File
@@ -14,6 +14,7 @@
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
<div>attribute interpolation: <input type="radio" ng-model="benchmarkType" value="interpolationAttr"></div>
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
<div>ngBind + filter: <input type="radio" ng-model="benchmarkType" value="ngBindFilter"></div>
@@ -46,6 +47,12 @@
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
</div>
</div>
<div ng-switch-when="interpolationAttr">
<h2>attribute interpolation</h2>
<div ng-repeat="row in data">
<span ng-repeat="column in row" i="{{column.i}}" j="{{column.j}}">i,j attrs</span>
</div>
</div>
<div ng-switch-when="ngBindFn">
<h2>bindings with functions</h2>
<div ng-repeat="row in data">
+1
View File
@@ -202,6 +202,7 @@ var generate = function(version, file) {
// publish for testing
exports.parseRawCommit = parseRawCommit;
exports.printSection = printSection;
// hacky start if not run by jasmine :-D
if (process.argv.join('').indexOf('jasmine-node') === -1) {
+62 -1
View File
@@ -1,4 +1,4 @@
/* global describe: false, it: false, expect: false */
/* global describe: false, beforeEach: false, afterEach: false, it: false, expect: false */
'use strict';
@@ -44,4 +44,65 @@ describe('changelog.js', function() {
expect(msg.breaking).toEqual(' first breaking change\nsomething else\nanother line with more info\n');
});
});
describe('printSection', function() {
var output;
var streamMock = {
write: function(str) {
output += str;
}
};
beforeEach(function() {
output = '';
});
it('should add a new line at the end of each breaking change list item ' +
'when there is 1 item per component', function() {
var title = 'test';
var printCommitLinks = false;
var section = {
module1: [{subject: 'breaking change 1'}],
module2: [{subject: 'breaking change 2'}]
};
var expectedOutput =
'\n' + '## test\n\n' +
'- **module1:** breaking change 1\n' +
'- **module2:** breaking change 2\n' +
'\n';
ch.printSection(streamMock, title, section, printCommitLinks);
expect(output).toBe(expectedOutput);
});
it('should add a new line at the end of each breaking change list item ' +
'when there are multiple items per component', function() {
var title = 'test';
var printCommitLinks = false;
var section = {
module1: [
{subject: 'breaking change 1.1'},
{subject: 'breaking change 1.2'}
],
module2: [
{subject: 'breaking change 2.1'},
{subject: 'breaking change 2.2'}
]
};
var expectedOutput =
'\n' + '## test\n\n' +
'- **module1:**\n' +
' - breaking change 1.1\n' +
' - breaking change 1.2\n' +
'- **module2:**\n' +
' - breaking change 2.1\n' +
' - breaking change 2.2\n' +
'\n';
ch.printSection(streamMock, title, section, printCommitLinks);
expect(output).toBe(expectedOutput);
});
});
});
+2 -2
View File
@@ -148,7 +148,7 @@ or JavaScript callbacks.
{@link ngAnimate CSS-based animations}
</td>
<td>
Follow ngAnimates CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined the animation can be triggered by referencing the CSS class within the HTML template code.
Follow ngAnimates CSS naming structure to reference CSS transitions / keyframe animations in AngularJS. Once defined, the animation can be triggered by referencing the CSS class within the HTML template code.
</td>
</tr>
<tr>
@@ -156,7 +156,7 @@ or JavaScript callbacks.
{@link ngAnimate JS-based animations}
</td>
<td>
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered the animation can be triggered by referencing the CSS class within the HTML template code.
Use {@link angular.Module#animation module.animation()} to register a JavaScript animation. Once registered, the animation can be triggered by referencing the CSS class within the HTML template code.
</td>
</tr>
</table>
@@ -1,17 +0,0 @@
@ngdoc error
@name $location:ihshprfx
@fullName Missing Hash Prefix
@description
This error occurs when {@link ng.$location $location} service is configured to use a hash prefix but this prefix was not present in a url that the `$location` service was asked to parse.
For example if you configure `$location` service with prefix `'!'`:
```
myApp.config(function($locationProvider) {
$locationProvider.hashPrefix('!');
});
```
If you enter the app at url `http://myapp.com/#/myView` this error will be thrown.
The correct url for this configuration is `http://myapp.com/#!/myView` (note the `'!'` after `'#'` symbol).
+9
View File
@@ -0,0 +1,9 @@
@ngdoc error
@name ng:test
@fullName Testability Not Found
@description
Angular's testability helper, getTestability, requires a root element to be
passed in. This helps differentiate between different Angular apps on the same
page. This error is thrown when no injector is found for root element. It is
often because the root element is outside of the ng-app.
+1 -1
View File
@@ -344,7 +344,7 @@ to anchors on the same page without needing to know on which page the user curre
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
to entry point of your application (e.g. index.html). Requiring a `<base>` tag is also important for
this case, as it allows Angular to differentiate between the part of the url that is the application
base and the path that should be handeled by the application.
base and the path that should be handled by the application.
### Sending links among different browsers
+8 -8
View File
@@ -31,12 +31,12 @@ added it as a dependency, you can test a few things:
##Supported directives
Currently, ngAria interfaces with the following directives:
* <a href="#ngmodel">ngModel</a>
* <a href="#ngdisabled">ngDisabled</a>
* <a href="#ngshow">ngShow</a>
* <a href="#nghide">ngHide</a>
* <a href="#ngclick-and-ngdblclick">ngClick</a>
* <a href="#ngclick-and-ngdblclick">ngDblClick</a>
* {@link guide/accessibility#ngmodel ngModel}
* {@link guide/accessibility#ngdisabled ngDisabled}
* {@link guide/accessibility#ngshow ngShow}
* {@link guide/accessibility#nghide ngHide}
* {@link guide/accessibility#ngclick ngClick}
* {@link guide/accessibility#ngdblclick ngDblClick}
<h2 id="ngmodel">ngModel</h2>
@@ -206,9 +206,9 @@ shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redundant. It toggles
`aria-hidden` on the directive when it is hidden or shown, but the content is already hidden with
`display: none`. See explanation for <a href="#ngshow">ngShow</a> when overriding the default CSS.
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
<h2 id="ngclick-and-ngdblclick">ngClick and ngDblclick</h2>
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
+1 -1
View File
@@ -200,7 +200,7 @@ code is present, and match the CSS class name on the element, then AngularJS wil
## Class and ngClass animation hooks
AngularJS also pays attention to CSS class changes on elements by triggering the **add** and **remove** hooks.
This means that if a CSS class is added to or removed from an element then an animation can be executed in between
This means that if a CSS class is added to or removed from an element then an animation can be executed in between,
before the CSS class addition or removal is finalized. (Keep in mind that AngularJS will only be
able to capture class changes if an **expression** or the **ng-class** directive is used on the element.)
+1 -1
View File
@@ -326,7 +326,7 @@ describe('state', function() {
expect(childScope.timeOfDay).toBe('morning');
expect(childScope.name).toBe('Mattie');
expect(grandChildScope.timeOfDay).toBe('evening');
expect(grandChildScope.name).toBe('Gingerbreak Baby');
expect(grandChildScope.name).toBe('Gingerbread Baby');
});
});
```
+11 -6
View File
@@ -13,21 +13,26 @@ Angular sets these CSS classes. It is up to your application to provide useful s
is defined. (see {@link guide/scope scope} guide for more information about scopes)
* `ng-isolate-scope`
- **Usage:** angular applies this class to any element for which a new
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
- **Usage:** angular applies this class to any element for which a new
{@link guide/directive#isolating-the-scope-of-a-directive isolate scope} is defined.
* `ng-binding`
- **Usage:** angular applies this class to any element that is attached to a data binding, via `ng-bind` or
`{{}}` curly braces, for example. (see {@link guide/databinding databinding} guide)
* `ng-invalid`, `ng-valid`
- **Usage:** angular applies this class to an input widget element if that element's input does
- **Usage:** angular applies this class to a form control widget element if that element's input does
not pass validation. (see {@link ng.directive:input input} directive)
* `ng-pristine`, `ng-dirty`
- **Usage:** angular {@link ng.directive:input input} directive applies `ng-pristine` class
to a new input widget element which did not have user interaction. Once the user interacts with
the input widget the class is changed to `ng-dirty`.
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-pristine` class
to a new form control widget which did not have user interaction. Once the user interacts with
the form control, the class is changed to `ng-dirty`.
* `ng-touched`, `ng-untouched`
- **Usage:** angular {@link ng.directive:ngModel ngModel} directive applies `ng-untouched` class
to a new form control widget which has not been blurred. Once the user blurs the form control,
the class is changed to `ng-touched`.
## Related Topics
+9
View File
@@ -174,6 +174,15 @@ For example, we could fix the example above by instead writing:
</svg>
```
If one wants to modify a camelcased attribute (SVG elements have valid camelcased attributes), such as `viewBox` on the `svg` element, one can use underscores to denote that the attribute to bind to is naturally camelcased.
For example, to bind to `viewBox`, we can write:
```html
<svg ng-attr-view_box="{{viewBox}}">
</svg>
```
## Creating Directives
+6 -4
View File
@@ -298,14 +298,16 @@ then the expression is not fulfilled and will remain watched.
### How to benefit from one-time binding
When interpolating text or attributes. If the expression, once set, will not change
then it is a candidate for one-time expression.
If the expression will not change once set, it is a candidate for one-time binding.
Here are three example cases.
When interpolating text or attributes:
```html
<div name="attr: {{::color}}">text: {{::name}}</div>
```
When using a directive with bidirectional binding and the parameters will not change
When using a directive with bidirectional binding and the parameters will not change:
```js
someModule.directive('someDirective', function() {
@@ -324,7 +326,7 @@ someModule.directive('someDirective', function() {
```
When using a directive that takes an expression
When using a directive that takes an expression:
```html
<ul>
+42 -31
View File
@@ -27,8 +27,8 @@ for other directives to augment its behavior.
E-mail: <input type="email" ng-model="user.email" /><br />
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
<button ng-click="reset()">RESET</button>
<button ng-click="update(user)">SAVE</button>
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="update(user)" value="Save" />
</form>
<pre>form = {{user | json}}</pre>
<pre>master = {{master | json}}</pre>
@@ -77,29 +77,29 @@ To allow styling of form as well as controls, `ngModel` adds these CSS classes:
The following example uses the CSS to display validity of each form control.
In the example both `user.name` and `user.email` are required, but are rendered
with red background only when they are dirty. This ensures that the user is not distracted
with an error until after interacting with the control, and failing to satisfy its validity.
with red background only after the input is blurred (loses focus).
This ensures that the user is not distracted with an error until after interacting with the control,
and failing to satisfy its validity.
<example module="formExample">
<file name="index.html">
<div ng-controller="ExampleController">
<form novalidate class="css-form">
Name:
<input type="text" ng-model="user.name" required /><br />
Name: <input type="text" ng-model="user.name" required /><br />
E-mail: <input type="email" ng-model="user.email" required /><br />
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
<button ng-click="reset()">RESET</button>
<button ng-click="update(user)">SAVE</button>
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="update(user)" value="Save" />
</form>
</div>
<style type="text/css">
.css-form input.ng-invalid.ng-dirty {
.css-form input.ng-invalid.ng-touched {
background-color: #FA787E;
}
.css-form input.ng-valid.ng-dirty {
.css-form input.ng-valid.ng-touched {
background-color: #78FA89;
}
</style>
@@ -140,34 +140,45 @@ the view using the standard binding primitives.
This allows us to extend the above example with these features:
- RESET button is enabled only if form has some changes
- SAVE button is enabled only if form has some changes and is valid
- custom error messages for `user.email` and `user.agree`
- Custom error message displayed after the user interacted with a control (i.e. when `$touched` is set)
- Custom error message displayed upon submitting the form (`$submitted` is set), even if the user
didn't interact with a control
<example module="formExample">
<file name="index.html">
<div ng-controller="ExampleController">
<form name="form" class="css-form" novalidate>
Name:
<input type="text" ng-model="user.name" name="uName" required /><br />
<input type="text" ng-model="user.name" name="uName" required="" />
<br />
<div ng-show="form.$submitted || form.uName.$touched">
<div ng-show="form.uName.$error.required">Tell us your name.</div>
</div>
E-mail:
<input type="email" ng-model="user.email" name="uEmail" required/><br />
<div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid:
<input type="email" ng-model="user.email" name="uEmail" required="" />
<br />
<div ng-show="form.$submitted || form.uEmail.$touched">
<span ng-show="form.uEmail.$error.required">Tell us your email.</span>
<span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
</div>
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
Gender:
<input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female
<br />
<input type="checkbox" ng-model="user.agree" name="userAgree" required="" />
<input type="checkbox" ng-model="user.agree" name="userAgree" required />
I agree: <input ng-show="user.agree" type="text" ng-model="user.agreeSign"
required /><br />
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
I agree:
<input ng-show="user.agree" type="text" ng-model="user.agreeSign" required="" />
<br />
<div ng-show="form.$submitted || form.userAgree.$touched">
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
</div>
<button ng-click="reset()" ng-disabled="isUnchanged(user)">RESET</button>
<button ng-click="update(user)"
ng-disabled="form.$invalid || isUnchanged(user)">SAVE</button>
<input type="button" ng-click="reset(form)" value="Reset" />
<input type="submit" ng-click="update(user)" value="Save" />
</form>
</div>
</file>
@@ -181,14 +192,14 @@ This allows us to extend the above example with these features:
$scope.master = angular.copy(user);
};
$scope.reset = function() {
$scope.reset = function(form) {
if (form) {
form.$setPristine();
form.$setUntouched();
}
$scope.user = angular.copy($scope.master);
};
$scope.isUnchanged = function(user) {
return angular.equals(user, $scope.master);
};
$scope.reset();
}]);
</file>
@@ -282,7 +293,7 @@ after last change.
Angular provides basic implementation for most common HTML5 {@link ng.directive:input input}
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url},
{@link input[email] email}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
{@link input[email] email}, {@link input[date] date}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`,
`min`, `max`).
+5 -184
View File
@@ -6,7 +6,7 @@
# Internet Explorer Compatibility
<div class="alert alert-warning">
**Note:** AngularJS 1.3 is dropping support for IE8. Read more about it on
**Note:** AngularJS 1.3 has dropped support for IE8. Read more about it on
[our blog](http://blog.angularjs.org/2013/12/angularjs-13-new-release-approaches.html).
AngularJS 1.2 will continue to support IE8, but the core team does not plan to spend time
addressing issues specific to IE8 or earlier.
@@ -14,7 +14,7 @@ addressing issues specific to IE8 or earlier.
This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML
attributes and tags. Read this document if you are planning on deploying your Angular application
on IE8 or earlier.
on IE.
The project currently supports and will attempt to fix bugs for IE9 and above. The continuous
integration server runs all the tests against IE9, IE10, and IE11. See
@@ -25,186 +25,7 @@ We do not run tests on IE8 and below. A subset of the AngularJS functionality ma
browsers, but it is up to you to test and decide whether it works for your particular app.
## Short Version
To make your Angular application work on IE please make sure that:
1. You polyfill JSON.stringify for IE7 and below. You can use
[JSON2](https://github.com/douglascrockford/JSON-js) or
[JSON3](http://bestiejs.github.com/json3/) polyfills for this.
```html
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<head>
<!--[if lte IE 7]>
<script src="/path/to/json2.js"></script>
<![endif]-->
</head>
<body>
...
</body>
</html>
```
2. add `id="ng-app"` to the root element in conjunction with `ng-app` attribute
```html
<!doctype html>
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
...
</html>
```
3. you **do not** use custom element tags such as `<ng:view>` (use the attribute version
`<div ng-view>` instead), or
4. if you **do use** custom element tags, then you must take these steps to make IE 8 and below happy:
```html
<!doctype html>
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
<head>
<!--[if lte IE 8]>
<script>
document.createElement('ng-include');
document.createElement('ng-pluralize');
document.createElement('ng-view');
// Optionally these for CSS
document.createElement('ng:include');
document.createElement('ng:pluralize');
document.createElement('ng:view');
</script>
<![endif]-->
</head>
<body>
...
</body>
</html>
```
5. Use `ng-style` tags instead of `style="{{ someCss }}"`. The later works in Chrome and Firefox
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
The **important** parts are:
* `xmlns:ng` - *namespace* - you need one namespace for each custom tag you are planning on
using.
* `document.createElement(yourTagName)` - *creation of custom tag names* - Since this is an
issue only for older version of IE you need to load it conditionally. For each tag which does
not have namespace and which is not defined in HTML you need to pre-declare it to make IE
happy.
## Long Version
IE has issues with element tag names which are not standard HTML tag names. These fall into two
categories, and each category has its own fix.
* If the tag name starts with `my:` prefix then it is considered an XML namespace and must
have corresponding namespace declaration on `<html xmlns:my="ignored">`
* If the tag has no `:` but it is not a standard HTML tag, then it must be pre-created using
`document.createElement('my-tag')`
* If you are planning on styling the custom tag with CSS selectors, then it must be
pre-created using `document.createElement('my-tag')` regardless of XML namespace.
## The Good News
The good news is that these restrictions only apply to element tag names, and not to element
attribute names. So this requires no special handling in IE: `<div my-tag your:tag></div>`.
## What happens if I fail to do this?
Suppose you have HTML with unknown tag `mytag` (this could also be `my:tag` or `my-tag` with same
result):
```html
<html>
<body>
<mytag>some text</mytag>
</body>
</html>
```
It should parse into the following DOM:
```
#document
+- HTML
+- BODY
+- mytag
+- #text: some text
```
The expected behavior is that the `BODY` element has a child element `mytag`, which in turn has
the text `some text`.
But this is not what IE does (if the above fixes are not included):
```
#document
+- HTML
+- BODY
+- mytag
+- #text: some text
+- /mytag
```
In IE, the behavior is that the `BODY` element has three children:
1. A self closing `mytag`. Example of self closing tag is `<br/>`. The trailing `/` is optional,
but the `<br>` tag is not allowed to have any children, and browsers consider `<br>some
text</br>` as three siblings not a `<br>` with `some text` as child.
2. A text node with `some text`. This should have been a child of `mytag` above, not a sibling.
3. A corrupt self closing `/mytag`. This is corrupt since element names are not allowed to have
the `/` character. Furthermore this closing element should not be part of the DOM since it is
only used to delineate the structure of the DOM.
## CSS Styling of Custom Tag Names
To make CSS selectors work with custom elements, the custom element name must be pre-created with
`document.createElement('my-tag')` regardless of XML namespace.
```html
<html xmlns:ng="needed for ng: namespace">
<head>
<!--[if lte IE 8]>
<script>
// needed to make ng-include parse properly
document.createElement('ng-include');
// needed to enable CSS reference
document.createElement('ng:view');
</script>
<![endif]-->
<style>
ng\:view {
display: block;
border: 1px solid red;
}
ng-include {
display: block;
border: 1px solid blue;
}
</style>
</head>
<body>
<ng:view></ng:view>
<ng-include></ng-include>
...
</body>
</html>
```
To ensure your Angular application works on IE please consider:
1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
+2 -2
View File
@@ -117,12 +117,12 @@ This is a short list of libraries with specific support and documentation for wo
### Courses
* **Free online:**
[thinkster.io](http://thinkster.io),
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1),
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
* **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)
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html),
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
* **Paid onsite:**
[angularbootcamp.com](http://angularbootcamp.com/)
+254 -140
View File
@@ -15,28 +15,68 @@ which drives many of these changes.
# Migrating from 1.2 to 1.3
- **$parse:**
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
## Angular Expression Parsing (`$parse` + `$interpolate`)
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
This is to disallow changing the behaviour of existing functions
in an unforseen fashion.
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
The (deprecated) __proto__ property does not work inside angular expressions
anymore.
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
- due to [48fa3aad](https://github.com/angular/angular.js/commit/48fa3aadd546036c7e69f71046f659ab1de244c6),
This prevents the use of __{define,lookup}{Getter,Setter}__ inside angular
expressions. If you really need them for some reason, please wrap/bind them to make them
less dangerous, then make them available through the scope object.
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
- due to [528be29d](https://github.com/angular/angular.js/commit/528be29d1662122a34e204dd607e1c0bd9c16bbc),
This prevents the use of `Object` inside angular expressions.
If you need Object.keys, make it accessible in the scope.
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
- due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
- due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
It can no longer be turned on.
Two methods have been removed:
* `$parseProvider.unwrapPromises`
* `$parseProvider.logPromiseWarnings`
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
the function returned by `$interpolate`
no longer has a `.parts` array set on it.
Instead it has two arrays:
* `.expressions`, an array of the expressions in the
interpolated text. The expressions are parsed with
`$parse`, with an extra layer converting them to strings
when computed
* `.separators`, an array of strings representing the
separations between interpolations in the text.
This array is **always** 1 item longer than the
`.expressions` array for easy merging with it
## Miscellaneous Angular helpers
- **Angular.copy:** due to [b59b04f9](https://github.com/angular/angular.js/commit/b59b04f98a0b59eead53f6a53391ce1bbcbe9b57),
This changes `angular.copy` so that it applies the prototype of the original
object to the copied object. Previously, `angular.copy` would copy properties
of the original object's prototype chain directly onto the copied object.
@@ -53,17 +93,54 @@ not filter them with `hasOwnProperty`.
**Be aware that this change also uses a feature that is not compatible with
IE8.** If you need this to work on IE8 then you would need to provide a polyfill
for `Object.create` and `Object.getPrototypeOf`.
- **core:** due to [bdfc9c02](https://github.com/angular/angular.js/commit/bdfc9c02d021e08babfbc966a007c71b4946d69d),
values 'f', '0', 'false', 'no', 'n', '[]' are no longer
treated as falsy. Only JavaScript falsy values are now treated as falsy by the
expression parser; there are six of them: false, null, undefined, NaN, 0 and "".
Closes #3969
Closes #4277
Closes #7960
- **$compile:** due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
forEach will iterate only over the initial number of items in
the array. So if items are added to the array during the iteration, these won't
be iterated over during the initial forEach call.
This change also makes our forEach behave more like Array#forEach.
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
If you expected `toJson` to strip these types of properties before, you will have to
manually do this yourself now.
## jqLite / JQuery
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
previously it was possible to set jqLite data on Text/Comment
nodes, but now that is allowed only on Element and Document nodes just like in
jQuery. We don't expect that app code actually depends on this accidental feature.
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
the jQuery `detach()` method does not trigger the `$destroy` event.
If you want to destroy Angular data attached to the element, use `remove()`.
## Angular HTML Compiler (`$compile`)
- due to [2ee29c5d](https://github.com/angular/angular.js/commit/2ee29c5da81ffacdc1cabb438f5d125d5e116cb9),
The isolated scope of a component directive no longer leaks into the template
that contains the instance of the directive. This means that you can no longer
access the isolated scope from attributes on the element where the isolated
directive is defined.
See https://github.com/angular/angular.js/issues/10236 for an example.
- due to [2cde927e](https://github.com/angular/angular.js/commit/2cde927e58c8d1588569d94a797e43cdfbcedaf9),
Requesting isolate scope and any other scope on a single element is an error.
@@ -77,9 +154,50 @@ If you find that your code is now throwing a `$compile:multidir` error,
check that you do not have directives on the same element that are trying
to request both an isolate and a non-isolate scope and fix your code.
Closes #4402
Closes #4421
- **NgModel:** due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
- due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
replace the element that they are on will be removed in the next major angular version.
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
problems compared to what it solves. Also, with Web Components it is normal to have
custom elements in the DOM.
- due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
calling `attr.$observe` no longer returns the observer function, but a
deregistration function instead. To migrate the code follow the example below:
Before:
directive('directiveName', function() {
return {
link: function(scope, elm, attr) {
var observer = attr.$observe('someAttr', function(value) {
console.log(value);
});
}
};
});
After:
directive('directiveName', function() {
return {
link: function(scope, elm, attr) {
var observer = function(value) {
console.log(value);
};
attr.$observe('someAttr', observer);
}
};
});
## Forms, Inputs and ngModel
- due to [1be9bb9d](https://github.com/angular/angular.js/commit/1be9bb9d3527e0758350c4f7417a4228d8571440),
If an expression is used on ng-pattern (such as `ng-pattern="exp"`) or on the
@@ -95,45 +213,70 @@ this limitation, use a regular expression object as the value for the expression
//after
$scope.exp = /abc/i;
- **Scope:** due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
This commit changes the API on `NgModelController`, both semantically and
in terms of adding and renaming methods.
* `$setViewValue(value)` -
This method still changes the `$viewValue` but does not immediately commit this
change through to the `$modelValue` as it did previously.
Now the value is committed only when a trigger specified in an associated
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
specified for the trigger then the change will also be debounced before being
committed.
In most cases this should not have a significant impact on how `NgModelController`
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
a (potentially debounced) commit immediately.
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
to cancel any pending debounced updates and to re-render the input.
To migrate code that used `$cancelUpdate()` follow the example below:
Before:
```js
$scope.resetWithCancel = function (e) {
if (e.keyCode == 27) {
$scope.myForm.myInput1.$cancelUpdate();
$scope.myValue = '';
}
};
```
After:
```js
$scope.resetWithCancel = function (e) {
if (e.keyCode == 27) {
$scope.myForm.myInput1.$rollbackViewValue();
$scope.myValue = '';
}
}
```
- types date, time, datetime-local, month, week now always
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
[#5864](https://github.com/angular/angular.js/issues/5864))
## Scopes and Digests (`$scope`)
- due to [8c6a8171](https://github.com/angular/angular.js/commit/8c6a8171f9bdaa5cdabc0cc3f7d3ce10af7b434d),
Scope#$id is now of type number rather than string. Since the
id is primarily being used for debugging purposes this change should not affect
anyone.
- **forEach:** due to [55991e33](https://github.com/angular/angular.js/commit/55991e33af6fece07ea347a059da061b76fc95f5),
forEach will iterate only over the initial number of items in
the array. So if items are added to the array during the iteration, these won't
be iterated over during the initial forEach call.
This change also makes our forEach behave more like Array#forEach.
- **jqLite:** due to [a196c8bc](https://github.com/angular/angular.js/commit/a196c8bca82a28c08896d31f1863cf4ecd11401c),
previously it was possible to set jqLite data on Text/Comment
nodes, but now that is allowed only on Element and Document nodes just like in
jQuery. We don't expect that app code actually depends on this accidental feature.
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
If you expected `$resource` to strip these types of properties before,
you will have to manually do this yourself now.
- **angular.toJson:** due to [c054288c](https://github.com/angular/angular.js/commit/c054288c9722875e3595e6e6162193e0fb67a251),
If you expected `toJson` to strip these types of properties before,
you will have to manually do this yourself now.
- **$compile:** due to [eec6394a](https://github.com/angular/angular.js/commit/eec6394a342fb92fba5270eee11c83f1d895e9fb), The `replace` flag for defining directives that
replace the element that they are on will be removed in the next major angular version.
This feature has difficult semantics (e.g. how attributes are merged) and leads to more
problems compared to what it solves. Also, with Web Components it is normal to have
custom elements in the DOM.
- **$parse:** due to [fa6e411d](https://github.com/angular/angular.js/commit/fa6e411da26824a5bae55f37ce7dbb859653276d),
promise unwrapping has been removed. It has been deprecated since 1.2.0-rc.3.
It can no longer be turned on.
Two methods have been removed:
* `$parseProvider.unwrapPromises`
* `$parseProvider.logPromiseWarnings`
- **Scope:** due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
- due to [82f45aee](https://github.com/angular/angular.js/commit/82f45aee5bd84d1cc53fb2e8f645d2263cdaacbc),
[#7445](https://github.com/angular/angular.js/issues/7445),
[#7523](https://github.com/angular/angular.js/issues/7523)
`$broadcast` and `$emit` will now reset the `currentScope` property of the event to
@@ -141,11 +284,11 @@ jQuery. We don't expect that app code actually depends on this accidental featur
`currentScope` property, it should be migrated to use `targetScope` instead. All of these cases
should be considered programming bugs.
- **jqLite:** due to [d71dbb1a](https://github.com/angular/angular.js/commit/d71dbb1ae50f174680533492ce4c7db3ff74df00),
the jQuery `detach()` method does not trigger the `$destroy` event.
If you want to destroy Angular data attached to the element, use `remove()`.
## Server Requests (`$http`, `$resource`)
- **$http:** due to [ad4336f9](https://github.com/angular/angular.js/commit/ad4336f9359a073e272930f8f9bcd36587a8648f),
@@ -197,7 +340,24 @@ More details on the new interceptors API (which has been around as of v1.1.4) ca
{@link $http#interceptors interceptors}
- **injector:** due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
correct events to detect errors, i.e. even empty responses can be successful.
- **$resource:** due to [d3c50c84](https://github.com/angular/angular.js/commit/d3c50c845671f0f8bcc3f7842df9e2fb1d1b1c40),
If you expected `$resource` to strip these types of properties before,
you will have to manually do this yourself now.
## Modules and Injector (`$inject`)
- due to [c0b4e2db](https://github.com/angular/angular.js/commit/c0b4e2db9cbc8bc3164cedc4646145d3ab72536e),
Previously, config blocks would be able to control behaviour of provider registration, due to being
invoked prior to provider registration. Now, provider registration always occurs prior to configuration
@@ -234,66 +394,13 @@ and "$dependentProvider" would have actually accomplished something and changed
app. This is no longer possible within a single module.
- **ngModelOptions:** due to [adfc322b](https://github.com/angular/angular.js/commit/adfc322b04a58158fb9697e5b99aab9ca63c80bb),
This commit changes the API on `NgModelController`, both semantically and
in terms of adding and renaming methods.
* `$setViewValue(value)` -
This method still changes the `$viewValue` but does not immediately commit this
change through to the `$modelValue` as it did previously.
Now the value is committed only when a trigger specified in an associated
`ngModelOptions` directive occurs. If `ngModelOptions` also has a `debounce` delay
specified for the trigger then the change will also be debounced before being
committed.
In most cases this should not have a significant impact on how `NgModelController`
is used: If `updateOn` includes `default` then `$setViewValue` will trigger
a (potentially debounced) commit immediately.
* `$cancelUpdate()` - is renamed to `$rollbackViewValue()` and has the same meaning,
which is to revert the current `$viewValue` back to the `$lastCommittedViewValue`,
to cancel any pending debounced updates and to re-render the input.
To migrate code that used `$cancelUpdate()` follow the example below:
Before:
```js
$scope.resetWithCancel = function (e) {
if (e.keyCode == 27) {
$scope.myForm.myInput1.$cancelUpdate();
$scope.myValue = '';
}
};
```
After:
```js
$scope.resetWithCancel = function (e) {
if (e.keyCode == 27) {
$scope.myForm.myInput1.$rollbackViewValue();
$scope.myValue = '';
}
}
```
- **$interpolate:** due to [88c2193c](https://github.com/angular/angular.js/commit/88c2193c71954b9e7e7e4bdf636a2b168d36300d),
the function returned by `$interpolate`
no longer has a `.parts` array set on it.
Instead it has two arrays:
* `.expressions`, an array of the expressions in the
interpolated text. The expressions are parsed with
`$parse`, with an extra layer converting them to strings
when computed
* `.separators`, an array of strings representing the
separations between interpolations in the text.
This array is **always** 1 item longer than the
`.expressions` array for easy merging with it
## Animation (`ngAnimate`)
- **$animate:** due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
- due to [1cb8584e](https://github.com/angular/angular.js/commit/1cb8584e8490ecdb1b410a8846c4478c6c2c0e53),
`$animate` will no longer default the after parameter to the last element of the parent
container. Instead, when after is not specified, the new element will be inserted as the
first child of the parent container.
@@ -308,7 +415,7 @@ to:
- **$animate:** due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
- due to [1bebe36a](https://github.com/angular/angular.js/commit/1bebe36aa938890d61188762ed618b1b5e193634),
Any class-based animation code that makes use of transitions
and uses the setup CSS classes (such as class-add and class-remove) must now
@@ -343,45 +450,52 @@ After:
Please view the documentation for ngAnimate for more info.
- **$compile:** due to [299b220f](https://github.com/angular/angular.js/commit/299b220f5e05e1d4e26bfd58d0b2fd7329ca76b1),
calling `attr.$observe` no longer returns the observer function, but a
deregistration function instead. To migrate the code follow the example below:
## Testing
- due to [85880a64](https://github.com/angular/angular.js/commit/85880a64900fa22a61feb926bf52de0965332ca5), some deprecated features of
Protractor tests no longer work.
`by.binding(descriptor)` no longer allows using the surrounding interpolation
markers in the descriptor (the default interpolation markers are `{{}}`).
Previously, these were optional.
Before:
directive('directiveName', function() {
return {
link: function(scope, elm, attr) {
var observer = attr.$observe('someAttr', function(value) {
console.log(value);
});
}
};
});
var el = element(by.binding('{{foo}}'));
After:
directive('directiveName', function() {
return {
link: function(scope, elm, attr) {
var observer = function(value) {
console.log(value);
};
var el = element(by.binding('foo'));
attr.$observe('someAttr', observer);
}
};
});
Prefixes `ng_` and `x-ng-` are no longer allowed for models. Use `ng-model`.
- **$httpBackend:** due to [6680b7b9](https://github.com/angular/angular.js/commit/6680b7b97c0326a80bdccaf0a35031e4af641e0e), the JSONP behavior for erroneous and empty responses changed:
Previously, a JSONP response was regarded as erroneous if it was empty. Now Angular is listening to the
correct events to detect errors, i.e. even empty responses can be successful.
`by.repeater` cannot find elements by row and column which are not children of
the row. For example, if your template is
- **build:** due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
<div ng-repeat="foo in foos">{{foo.name}}</div>
Before:
var el = element(by.repeater('foo in foos').row(2).column('foo.name'))
After:
You may either enclose `{{foo.name}}` in a child element
<div ng-repeat="foo in foos"><span>{{foo.name}}</span></div>
or simply use:
var el = element(by.repeater('foo in foos').row(2))
## Internet Explorer 8
- due to [eaa1d00b](https://github.com/angular/angular.js/commit/eaa1d00b24008f590b95ad099241b4003688cdda),
As communicated before, IE8 is no longer supported.
- **input:** types date, time, datetime-local, month, week now always
require a `Date` object as model ([46bd6dc8](https://github.com/angular/angular.js/commit/46bd6dc88de252886d75426efc2ce8107a5134e9),
[#5864](https://github.com/angular/angular.js/issues/5864))
+1 -1
View File
@@ -345,7 +345,7 @@ access on JavaScript object.
Dirty checking can be done with three strategies: By reference, by collection contents, and by value. The strategies differ in the kinds of changes they detect, and in their performance characteristics.
- Watching *by reference* ({@link
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient stategy.
ng.$rootScope.Scope#$watch scope.$watch} `(watchExpression, listener)`) detects a change when the whole value returned by the watch expression switches to a new value. If the value is an array or an object, changes inside it are not detected. This is the most efficient strategy.
- Watching *collection contents* ({@link
ng.$rootScope.Scope#$watchCollection scope.$watchCollection} `(watchExpression, listener)`) detects changes that occur inside an array or an object: When items are added, removed, or reordered. The detection is shallow - it does not reach into nested collections. Watching collection contents is more expensive than watching by reference, because copies of the collection contents need to be maintained. However, the strategy attempts to minimize the amount of copying required.
- Watching *by value* ({@link
+2 -2
View File
@@ -116,7 +116,7 @@ npm --version
```
<div class="alert alert-info">If you need to run a different versions of node.js
<div class="alert alert-info">If you need to run different versions of node.js
in your local environment, consider installing
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
Node Version Manager (nvm)
@@ -160,7 +160,7 @@ globally and run directly from a terminal/command prompt. You don't need to do t
tutorial, but if you decide you do want to run them directly, you can install these modules globally
using, `sudo npm install -g ...`.
For instance to install the Bower command line executable you would do:
For instance, to install the Bower command line executable you would do:
```
sudo npm install -g bower
+10 -6
View File
@@ -114,26 +114,30 @@ module.exports = function(config, specificOptions) {
var buildLabel = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
config.logLevel = config.LOG_DEBUG;
config.transports = ['websocket', 'xhr-polling'];
config.captureTimeout = 0; // rely on SL timeout
// Karma (with socket.io 1.x) buffers by 50 and 50 tests can take a long time on IEs;-)
config.browserNoActivityTimeout = 120000;
config.browserStack.build = buildLabel;
config.browserStack.startTunnel = false;
config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
config.sauceLabs.build = buildLabel;
config.sauceLabs.startConnect = false;
config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
config.sauceLabs.recordScreenshots = true;
// 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'];
// 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')
});
if (process.env.BROWSER_PROVIDER === 'saucelabs' || !process.env.BROWSER_PROVIDER) {
// Allocating a browser can take pretty long (eg. if we are out of capacity and need to wait
// for another build to finish) and so the `captureTimeout` typically kills
// an in-queue-pending request, which makes no sense.
config.captureTimeout = 0;
}
}
-1
View File
@@ -1 +0,0 @@
node ./lib/browser-stack/start-tunnel.js &
@@ -5,9 +5,10 @@ var http = require('http');
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
var HOSTNAME = 'localhost';
var PORTS = require('../grunt/utils').availablePorts;
var PORTS = [9876, 8000];
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
var READY_FILE = process.env.SAUCE_CONNECT_READY_FILE;
var READY_FILE = process.env.BROWSER_PROVIDER_READY_FILE;
var TUNNEL_IDENTIFIER = process.env.TRAVIS_JOB_NUMBER;
// We need to start fake servers, otherwise the tunnel does not start.
var fakeServers = [];
@@ -24,6 +25,7 @@ PORTS.forEach(function(port) {
var tunnel = new BrowserStackTunnel({
key: ACCESS_KEY,
tunnelIdentifier: TUNNEL_IDENTIFIER,
hosts: hosts
});
+3
View File
@@ -0,0 +1,3 @@
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
node ./lib/browserstack/start_tunnel.js &
+1 -43
View File
@@ -11,21 +11,6 @@ var _ = require('lodash');
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 = {
@@ -312,32 +297,5 @@ module.exports = {
}
next();
};
},
parallelTask: function(args, options) {
var task = {
grunt: true,
args: args,
stream: options && options.stream
};
args.push('--port=' + this.sauceLabsAvailablePorts.pop());
if (args.indexOf('test:e2e') !== -1 && grunt.option('e2e-browsers')) {
args.push('--browsers=' + grunt.option('e2e-browsers'));
} else if (grunt.option('browsers')) {
args.push('--browsers=' + grunt.option('browsers'));
}
if (grunt.option('reporters')) {
args.push('--reporters=' + grunt.option('reporters'));
}
return task;
},
// see http://saucelabs.com/docs/connect#localhost
sauceLabsAvailablePorts: [9000, 9001, 9080, 9090, 9876],
// pseudo-random port numbers for BrowserStack
availablePorts: getRandomPorts()
}
};
+2 -3
View File
@@ -25,7 +25,6 @@
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs": "~0.7.1",
"grunt-merge-conflict": "~0.0.1",
"grunt-parallel": "~0.3.1",
"grunt-shell": "~1.1.1",
"gulp": "~3.8.0",
"gulp-concat": "^2.4.1",
@@ -38,8 +37,8 @@
"jasmine-node": "~1.14.5",
"jasmine-reporters": "~1.0.1",
"jshint-stylish": "~1.0.0",
"karma": "^0.12.0",
"karma-browserstack-launcher": "0.1.1",
"karma": "vojtajina/karma#socketio_10",
"karma-browserstack-launcher": "0.1.2",
"karma-chrome-launcher": "0.1.5",
"karma-firefox-launcher": "0.1.3",
"karma-jasmine": "0.1.5",
+72 -23
View File
@@ -2,32 +2,81 @@
var config = require('./protractor-shared-conf').config;
config.sauceUser = process.env.SAUCE_USERNAME;
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
if (process.env.BROWSER_PROVIDER === 'browserstack') {
// Using BrowserStack.
config.seleniumAddress = 'http://hub.browserstack.com/wd/hub';
config.multiCapabilities = [
capabilitiesForBrowserStack({
browserName: 'chrome',
platform: 'MAC',
version: '34'
}),
capabilitiesForBrowserStack({
browserName: 'firefox',
version: '28'
}),
capabilitiesForBrowserStack({
browserName: 'safari',
platform: 'MAC',
version: '7'
})
];
} else {
// Using SauceLabs.
config.sauceUser = process.env.SAUCE_USERNAME;
config.sauceKey = process.env.SAUCE_ACCESS_KEY;
config.multiCapabilities = [
capabilitiesForSauceLabs({
browserName: 'chrome',
platform: 'OS X 10.9',
version: '34'
}),
capabilitiesForSauceLabs({
browserName: 'firefox',
version: '28'
}),
capabilitiesForSauceLabs({
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
})
];
}
config.multiCapabilities = [{
'browserName': 'chrome',
'platform': 'OS X 10.9',
'name': 'Angular E2E',
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
'build': process.env.TRAVIS_BUILD_NUMBER,
'version': '34'
}, {
'browserName': 'firefox',
'name': 'Angular E2E',
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
'build': process.env.TRAVIS_BUILD_NUMBER,
'version': '28'
}, {
browserName: 'safari',
'platform': 'OS X 10.9',
'version': '7',
'name': 'Angular E2E',
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
'build': process.env.TRAVIS_BUILD_NUMBER
}];
config.allScriptsTimeout = 30000;
config.getPageTimeout = 30000;
exports.config = config;
function capabilitiesForBrowserStack(capabilities) {
return {
'browserstack.user': process.env.BROWSER_STACK_USERNAME,
'browserstack.key': process.env.BROWSER_STACK_ACCESS_KEY,
'browserstack.local' : 'true',
'browserstack.debug': 'true',
'browserstack.tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
'tunnelIdentifier': process.env.TRAVIS_JOB_NUMBER,
'name': 'Angular E2E',
'build': process.env.TRAVIS_BUILD_NUMBER,
'browserName': capabilities.browserName,
'platform': capabilities.platform,
'version': capabilities.version
};
}
function capabilitiesForSauceLabs(capabilities) {
return {
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
'name': 'Angular E2E',
'build': process.env.TRAVIS_BUILD_NUMBER,
'browserName': capabilities.browserName,
'platform': capabilities.platform,
'version': capabilities.version
};
}
+2
View File
@@ -16,9 +16,11 @@ function init {
REPOS=(
angular
angular-animate
angular-aria
angular-cookies
angular-i18n
angular-loader
angular-messages
angular-mocks
angular-route
angular-resource
+9 -2
View File
@@ -2,13 +2,20 @@
set -e
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
if [ $JOB = "unit" ]; then
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11"
else
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11"
fi
grunt test:promises-aplus
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
grunt test:unit --browsers $BROWSERS --reporters dots
grunt ci-checks
grunt tests:docs --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
grunt tests:docs --browsers $BROWSERS --reporters dots
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
elif [ $JOB = "e2e" ]; then
if [ $TEST_TARGET = "jquery" ]; then
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# Has to be run from project root directory.
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
echo "Using BrowserStack"
elif [ "$BROWSER_PROVIDER" == "saucelabs" ]; then
echo "Using SauceLabs"
else
echo "Invalid BROWSER_PROVIDER. Please set env var BROWSER_PROVIDER to 'saucelabs' or 'browserstack'."
exit 1
fi
./lib/${BROWSER_PROVIDER}/start_tunnel.sh
+1
View File
@@ -51,6 +51,7 @@
"isWindow": false,
"isScope": false,
"isFile": false,
"isFormData": false,
"isBlob": false,
"isBoolean": false,
"isPromiseLike": false,
+20 -5
View File
@@ -45,6 +45,7 @@
isWindow: true,
isScope: true,
isFile: true,
isFormData: true,
isBlob: true,
isBoolean: true,
isPromiseLike: true,
@@ -162,8 +163,8 @@ if ('i' !== 'I'.toLowerCase()) {
}
var /** holds major version number for IE or NaN for real browsers */
msie,
var
msie, // holds major version number for IE, or NaN if UA is not IE.
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
@@ -566,6 +567,11 @@ function isFile(obj) {
}
function isFormData(obj) {
return toString.call(obj) === '[object FormData]';
}
function isBlob(obj) {
return toString.call(obj) === '[object Blob]';
}
@@ -964,12 +970,16 @@ function toJsonReplacer(key, value) {
* stripped since angular uses this notation internally.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
* @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
* If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
* @returns {string|undefined} JSON-ified string representing `obj`.
*/
function toJson(obj, pretty) {
if (typeof obj === 'undefined') return undefined;
return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null);
if (!isNumber(pretty)) {
pretty = pretty ? 2 : null;
}
return JSON.stringify(obj, toJsonReplacer, pretty);
}
@@ -1424,7 +1434,12 @@ function reloadWithDebugInfo() {
* @param {DOMElement} element DOM element which is the root of angular application.
*/
function getTestability(rootElement) {
return angular.element(rootElement).injector().get('$$testability');
var injector = angular.element(rootElement).injector();
if (!injector) {
throw ngMinErr('test',
'no injector found for element argument to getTestability');
}
return injector.get('$$testability');
}
var SNAKE_CASE_REGEXP = /[A-Z]/g;
+4 -2
View File
@@ -83,7 +83,8 @@
$TimeoutProvider,
$$RAFProvider,
$$AsyncCallbackProvider,
$WindowProvider
$WindowProvider,
$$jqLiteProvider
*/
@@ -236,7 +237,8 @@ function publishExternalAPI(angular) {
$timeout: $TimeoutProvider,
$window: $WindowProvider,
$$rAF: $$RAFProvider,
$$asyncCallback: $$AsyncCallbackProvider
$$asyncCallback: $$AsyncCallbackProvider,
$$jqLite: $$jqLiteProvider
});
}
]);
+12 -8
View File
@@ -179,6 +179,7 @@ function annotate(fn, strictDi, name) {
* Return an instance of the service.
*
* @param {string} name The name of the instance to retrieve.
* @param {string} caller An optional string to provide the origin of the function call for error messages.
* @return {*} The instance.
*/
@@ -629,14 +630,17 @@ function createInjector(modulesToLoad, strictDi) {
}
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function() {
createInternalInjector(providerCache, function(serviceName, caller) {
if (angular.isString(caller)) {
path.push(caller);
}
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
@@ -671,7 +675,7 @@ function createInjector(modulesToLoad, strictDi) {
function enforceReturnValue(name, factory) {
return function enforcedReturnValue() {
var result = instanceInjector.invoke(factory, this, undefined, name);
var result = instanceInjector.invoke(factory, this);
if (isUndefined(result)) {
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
}
@@ -766,7 +770,7 @@ function createInjector(modulesToLoad, strictDi) {
function createInternalInjector(cache, factory) {
function getService(serviceName) {
function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
@@ -777,7 +781,7 @@ function createInjector(modulesToLoad, strictDi) {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName);
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
@@ -809,7 +813,7 @@ function createInjector(modulesToLoad, strictDi) {
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key)
: getService(key, serviceName)
);
}
if (isArray(fn)) {
+21
View File
@@ -1003,3 +1003,24 @@ forEach({
JQLite.prototype.bind = JQLite.prototype.on;
JQLite.prototype.unbind = JQLite.prototype.off;
});
// Provider for private $$jqLite service
function $$jqLiteProvider() {
this.$get = function $$jqLite() {
return extend(JQLite, {
hasClass: function(node, classes) {
if (node.attr) node = node[0];
return jqLiteHasClass(node, classes);
},
addClass: function(node, classes) {
if (node.attr) node = node[0];
return jqLiteAddClass(node, classes);
},
removeClass: function(node, classes) {
if (node.attr) node = node[0];
return jqLiteRemoveClass(node, classes);
}
});
};
}
+8 -1
View File
@@ -61,6 +61,11 @@ function Browser(window, document, $log, $sniffer) {
}
}
function getHash(url) {
var index = url.indexOf('#');
return index === -1 ? '' : url.substr(index + 1);
}
/**
* @private
* Note: this method is used only by scenario runner
@@ -190,8 +195,10 @@ function Browser(window, document, $log, $sniffer) {
}
if (replace) {
location.replace(url);
} else {
} else if (!sameBase) {
location.href = url;
} else {
location.hash = getHash(url);
}
}
return self;
+34 -20
View File
@@ -115,7 +115,7 @@
* #### `multiElement`
* When this property is set to true, the HTML compiler will collect DOM nodes between
* nodes with the attributes `directive-name-start` and `directive-name-end`, and group them
* together as the directive elements. It is recomended that this feature be used on directives
* together as the directive elements. It is recommended that this feature be used on directives
* which are not strictly behavioural (such as {@link ngClick}), and which
* do not manipulate or replace child nodes (such as {@link ngInclude}).
*
@@ -803,7 +803,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* 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.
* The sanitization is a security measure aimed at preventing 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`
@@ -870,7 +870,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* * `ng-binding` CSS class
* * `$binding` data property containing an array of the binding expressions
*
* You may want to use this in production for a significant performance boost. See
* You may want to disable this in production for a significant performance boost. See
* {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
*
* The default value is true.
@@ -907,6 +907,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
};
Attributes.prototype = {
/**
* @ngdoc method
* @name $compile.directive.Attributes#$normalize
* @kind function
*
* @description
* Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or
* `data-`) to its normalized, camelCase form.
*
* Also there is special case for Moz prefix starting with upper case letter.
*
* For further information check out the guide on {@link guide/directive#matching-directives Matching Directives}
*
* @param {string} name Name to normalize
*/
$normalize: directiveNormalize,
@@ -1405,7 +1420,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// support ngAttr attribute binding
ngAttrName = directiveNormalize(name);
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
name = snake_case(ngAttrName.substr(6), '-');
name = name.replace(PREFIX_REGEXP, '')
.substr(8).replace(/_(.)/g, function(match, letter) {
return letter.toUpperCase();
});
}
var directiveNName = ngAttrName.replace(/(Start|End)$/, '');
@@ -2317,7 +2335,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
var interpolateFn = $interpolate(value, true);
var trustedContext = getTrustedContext(node, name);
allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
// no interpolation found -> ignore
if (!interpolateFn) return;
@@ -2342,16 +2363,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
"ng- versions (such as ng-click instead of onclick) instead.");
}
// If the attribute was removed, then we are done
if (!attr[name]) {
return;
// If the attribute has changed since last $interpolate()ed
var newValue = attr[name];
if (newValue !== value) {
// we need to interpolate again since the attribute value has been updated
// (e.g. by another directive's compile function)
// ensure unset/empty values make interpolateFn falsy
interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing);
value = newValue;
}
// we need to interpolate again, in case the attribute value has been updated
// (e.g. by another directive's compile function)
interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name),
ALL_OR_NOTHING_ATTRS[name] || allOrNothing);
// if attribute was updated so that there is no interpolation going on we don't want to
// register any observers
if (!interpolateFn) return;
@@ -2485,13 +2506,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
/**
* Converts all accepted directives format into proper directive name.
* All of these will become 'myDirective':
* my:Directive
* my-directive
* x-my-directive
* data-my:directive
*
* Also there is special case for Moz prefix starting with upper case letter.
* @param name Name to normalize
*/
function directiveNormalize(name) {
+2 -3
View File
@@ -11,9 +11,8 @@
* make the link go to the wrong URL if the user clicks it before
* Angular has a chance to replace the `{{hash}}` markup with its
* value. Until Angular replaces the markup the link will be broken
* and will most likely return a 404 error.
*
* The `ngHref` directive solves this problem.
* and will most likely return a 404 error. The `ngHref` directive
* solves this problem.
*
* The wrong way to write it:
* ```html
+14 -14
View File
@@ -954,7 +954,6 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
var placeholder = element[0].placeholder, noevent = {};
var type = lowercase(element[0].type);
// In composition mode, users are still inputing intermediate text buffer,
@@ -974,19 +973,14 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
var listener = function(ev) {
if (timeout) {
$browser.defer.cancel(timeout);
timeout = null;
}
if (composing) return;
var value = element.val(),
event = ev && ev.type;
// IE (11 and under) seem to emit an 'input' event if the placeholder value changes.
// We don't want to dirty the value when this happens, so we abort here. Unfortunately,
// IE also sends input events for other non-input-related things, (such as focusing on a
// form control), so this change is not entirely enough to solve this.
if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) {
placeholder = element[0].placeholder;
return;
}
// By default we will trim the value
// If the attribute ng-trim exists we will avoid trimming
// If input type is 'password', the value is never trimmed
@@ -1009,11 +1003,13 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
} else {
var timeout;
var deferListener = function(ev) {
var deferListener = function(ev, input, origValue) {
if (!timeout) {
timeout = $browser.defer(function() {
listener(ev);
timeout = null;
if (!input || input.value !== origValue) {
listener(ev);
}
});
}
};
@@ -1025,7 +1021,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
// command modifiers arrows
if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
deferListener(event);
deferListener(event, this, this.value);
});
// if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
@@ -2200,11 +2196,15 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
var prevModelValue = ctrl.$modelValue;
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
ctrl.$$rawModelValue = modelValue;
if (allowInvalid) {
ctrl.$modelValue = modelValue;
writeToModelIfNeeded();
}
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
// This can happen if e.g. $setViewValue is called from inside a parser
ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
if (!allowInvalid) {
// Note: Don't check ctrl.$valid here, as we could have
// external validators (e.g. calculated on the server),
+1 -1
View File
@@ -257,7 +257,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var aliasAs = match[3];
var trackByExp = match[4];
match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);
if (!match) {
throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.",
+1 -1
View File
@@ -34,7 +34,7 @@
}]);
</script>
<div ng-controller="ExampleController">
<input ng-model="title"><br>
<input ng-model="title"> <br/>
<textarea ng-model="text"></textarea> <br/>
<pane title="{{title}}">{{text}}</pane>
</div>
-1
View File
@@ -40,7 +40,6 @@ var scriptDirective = ['$templateCache', function($templateCache) {
compile: function(element, attr) {
if (attr.type == 'text/ng-template') {
var templateUrl = attr.id,
// IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent
text = element[0].text;
$templateCache.put(templateUrl, text);
+20 -13
View File
@@ -42,9 +42,9 @@ var ngOptionsMinErr = minErr('ngOptions');
* or property name (for object data sources) of the value within the collection. If a `track by` expression
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
*
* ### `select as` with `trackexpr`
* ### `select as` with `track by`
*
* Using `select as` together with `trackexpr` is not recommended. Reasoning:
* Using `select as` together with `track by` is not recommended. Reasoning:
*
* - Example: &lt;select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"&gt;
* values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
@@ -69,8 +69,10 @@ var ngOptionsMinErr = minErr('ngOptions');
* * for array data sources:
* * `label` **`for`** `value` **`in`** `array`
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
* * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
* (for including a filter with `track by`)
* * for object data sources:
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
@@ -212,7 +214,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
self.removeOption = function(value) {
if (this.hasOption(value)) {
delete optionsMap[value];
if (ngModelCtrl.$viewValue == value) {
if (ngModelCtrl.$viewValue === value) {
this.renderUnknownOption(value);
}
}
@@ -679,18 +681,23 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
updateLabelMap(labelMap, option.label, false);
option.element.remove();
}
forEach(labelMap, function(count, label) {
if (count > 0) {
selectCtrl.addOption(label);
} else if (count < 0) {
selectCtrl.removeOption(label);
}
});
}
// remove any excessive OPTGROUPs from select
while (optionGroupsCache.length > groupIndex) {
optionGroupsCache.pop()[0].element.remove();
// remove all the labels in the option group
optionGroup = optionGroupsCache.pop();
for (index = 1; index < optionGroup.length; ++index) {
updateLabelMap(labelMap, optionGroup[index].label, false);
}
optionGroup[0].element.remove();
}
forEach(labelMap, function(count, label) {
if (count > 0) {
selectCtrl.addOption(label);
} else if (count < 0) {
selectCtrl.removeOption(label);
}
});
}
}
}
+86 -89
View File
@@ -119,104 +119,101 @@ function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;
var comparatorType = typeof(comparator),
predicates = [];
var predicateFn;
var matchAgainstAnyProp;
predicates.check = function(value, index) {
for (var j = 0; j < predicates.length; j++) {
if (!predicates[j](value, index)) {
return false;
}
}
return true;
};
if (comparatorType !== 'function') {
if (comparatorType === 'boolean' && comparator) {
comparator = function(obj, text) {
return angular.equals(obj, text);
};
} else {
comparator = function(obj, text) {
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
for (var objKey in obj) {
if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
comparator(obj[objKey], text[objKey])) {
return true;
}
}
return false;
}
text = ('' + text).toLowerCase();
return ('' + obj).toLowerCase().indexOf(text) > -1;
};
}
}
var search = function(obj, text) {
if (typeof text === 'string' && text.charAt(0) === '!') {
return !search(obj, text.substr(1));
}
switch (typeof obj) {
case 'boolean':
case 'number':
case 'string':
return comparator(obj, text);
case 'object':
switch (typeof text) {
case 'object':
return comparator(obj, text);
default:
for (var objKey in obj) {
if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
return true;
}
}
break;
}
return false;
case 'array':
for (var i = 0; i < obj.length; i++) {
if (search(obj[i], text)) {
return true;
}
}
return false;
default:
return false;
}
};
switch (typeof expression) {
case 'function':
predicateFn = expression;
break;
case 'boolean':
case 'number':
case 'string':
// Set up expression object and fall through
expression = {$:expression};
// jshint -W086
matchAgainstAnyProp = true;
//jshint -W086
case 'object':
// jshint +W086
for (var key in expression) {
(function(path) {
if (typeof expression[path] === 'undefined') return;
predicates.push(function(value) {
return search(path == '$' ? value : (value && value[path]), expression[path]);
});
})(key);
}
break;
case 'function':
predicates.push(expression);
//jshint +W086
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
break;
default:
return array;
}
var filtered = [];
for (var j = 0; j < array.length; j++) {
var value = array[j];
if (predicates.check(value, j)) {
filtered.push(value);
}
}
return filtered;
return array.filter(predicateFn);
};
}
// Helper functions for `filterFilter`
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
var predicateFn;
if (comparator === true) {
comparator = equals;
} else if (!isFunction(comparator)) {
comparator = function(actual, expected) {
if (isObject(actual) || isObject(expected)) {
// Prevent an object to be considered equal to a string like `'[object'`
return false;
}
actual = lowercase('' + actual);
expected = lowercase('' + expected);
return actual.indexOf(expected) !== -1;
};
}
predicateFn = function(item) {
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
};
return predicateFn;
}
function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
var actualType = typeof actual;
var expectedType = typeof expected;
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
} else if (actualType === 'array') {
// In case `actual` is an array, consider it a match
// if ANY of it's items matches `expected`
return actual.some(function(item) {
return deepCompare(item, expected, comparator, matchAgainstAnyProp);
});
}
switch (actualType) {
case 'object':
var key;
if (matchAgainstAnyProp) {
for (key in actual) {
if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
return true;
}
}
return false;
} else if (expectedType === 'object') {
for (key in expected) {
var expectedVal = expected[key];
if (isFunction(expectedVal)) {
continue;
}
var keyIsDollar = key === '$';
var actualVal = keyIsDollar ? actual : actual[key];
if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
return false;
}
}
return true;
} else {
return comparator(actual, expected);
}
break;
case 'function':
return false;
default:
return comparator(actual, expected);
}
}
+18 -13
View File
@@ -150,7 +150,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
if (numStr.indexOf('e') !== -1) {
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
numStr = '0';
number = 0;
} else {
formatedText = numStr;
@@ -171,10 +170,6 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
if (number === 0) {
isNegative = false;
}
var fraction = ('' + number).split(DECIMAL_SEP);
var whole = fraction[0];
fraction = fraction[1] || '';
@@ -207,12 +202,16 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
} else {
if (fractionSize > 0 && number > -1 && number < 1) {
if (fractionSize > 0 && number < 1) {
formatedText = number.toFixed(fractionSize);
number = parseFloat(formatedText);
}
}
if (number === 0) {
isNegative = false;
}
parts.push(isNegative ? pattern.negPre : pattern.posPre,
formatedText,
isNegative ? pattern.negSuf : pattern.posSuf);
@@ -357,8 +356,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
* * `'a'`: AM/PM marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
* * `'ww'`: ISO-8601 week of year (00-53)
* * `'w'`: ISO-8601 week of year (0-53)
* * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
* * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
*
* `format` string can also be one of the following predefined
* {@link guide/i18n localizable formats}:
@@ -502,25 +501,31 @@ function dateFilter($locale) {
* the binding is automatically converted to JSON.
*
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
* @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
* @returns {string} JSON string.
*
*
* @example
<example>
<file name="index.html">
<pre>{{ {'name':'value'} | json }}</pre>
<pre id="default-spacing">{{ {'name':'value'} | json }}</pre>
<pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre>
</file>
<file name="protractor.js" type="protractor">
it('should jsonify filtered objects', function() {
expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
});
</file>
</example>
*
*/
function jsonFilter() {
return function(object) {
return toJson(object, true);
return function(object, spacing) {
if (isUndefined(spacing)) {
spacing = 2;
}
return toJson(object, spacing);
};
}
+31 -6
View File
@@ -160,15 +160,40 @@ function orderByFilter($parse) {
? function(a, b) {return comp(b,a);}
: comp;
}
function isPrimitive(value) {
switch (typeof value) {
case 'number': /* falls through */
case 'boolean': /* falls through */
case 'string':
return true;
default:
return false;
}
}
function objectToString(value) {
if (value === null) return 'null';
if (typeof value.toString === 'function') {
value = value.toString();
if (isPrimitive(value)) return value;
}
if (typeof value.valueOf === 'function') {
value = value.valueOf();
if (isPrimitive(value)) return value;
}
return '';
}
function compare(v1, v2) {
var t1 = typeof v1;
var t2 = typeof v2;
if (t1 == t2) {
if (isDate(v1) && isDate(v2)) {
v1 = v1.valueOf();
v2 = v2.valueOf();
}
if (t1 == "string") {
if (t1 === t2 && t1 === "object") {
v1 = objectToString(v1);
v2 = objectToString(v2);
}
if (t1 === t2) {
if (t1 === "string") {
v1 = v1.toLowerCase();
v2 = v2.toLowerCase();
}
+67 -50
View File
@@ -2,23 +2,34 @@
var APPLICATION_JSON = 'application/json';
var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
var JSON_START = /^\s*(\[|\{[^\{])/;
var JSON_END = /[\}\]]\s*$/;
var JSON_START = /^\[|^\{(?!\{)/;
var JSON_ENDS = {
'[': /]$/,
'{': /}$/
};
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
// strip json vulnerability protection prefix
data = data.replace(JSON_PROTECTION_PREFIX, '');
var contentType = headers('Content-Type');
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0 && data.trim()) ||
(JSON_START.test(data) && JSON_END.test(data))) {
data = fromJson(data);
// Strip json vulnerability protection prefix and trim whitespace
var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
if (tempData) {
var contentType = headers('Content-Type');
if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
data = fromJson(tempData);
}
}
}
return data;
}
function isJsonLike(str) {
var jsonStart = str.match(JSON_START);
return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
}
/**
* Parse headers into key value object
*
@@ -81,16 +92,17 @@ function headersGetter(headers) {
* This function is used for both request and response transforming
*
* @param {*} data Data to transform.
* @param {function(string=)} headers Http headers getter fn.
* @param {function(string=)} headers HTTP headers getter fn.
* @param {number} status HTTP status code of the response.
* @param {(Function|Array.<Function>)} fns Function or an array of functions.
* @returns {*} Transformed data.
*/
function transformData(data, headers, fns) {
function transformData(data, headers, status, fns) {
if (isFunction(fns))
return fns(data, headers);
return fns(data, headers, status);
forEach(fns, function(fn) {
data = fn(data, headers);
data = fn(data, headers, status);
});
return data;
@@ -142,7 +154,7 @@ function $HttpProvider() {
// transform outgoing request data
transformRequest: [function(d) {
return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d;
return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
}],
// default headers
@@ -369,7 +381,7 @@ function $HttpProvider() {
*
* Both requests and responses can be transformed using transformation functions: `transformRequest`
* and `transformResponse`. These properties can be a single function that returns
* the transformed value (`{function(data, headersGetter)`) or an array of such transformation functions,
* the transformed value (`{function(data, headersGetter, status)`) or an array of such transformation functions,
* which allows you to `push` or `unshift` a new transformation function into the transformation chain.
*
* ### Default Transformations
@@ -610,12 +622,14 @@ function $HttpProvider() {
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version.
* See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
* See {@link ng.$http#overriding-the-default-transformations-per-request
* Overriding the Default Transformations}
* - **transformResponse**
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}`
* transform function or an array of such functions. The transform function takes the http
* response body and headers and returns its transformed (typically deserialized) version.
* See {@link #overriding-the-default-transformations-per-request Overriding the Default Transformations}
* response body, headers and status and returns its transformed (typically deserialized) version.
* See {@link ng.$http#overriding-the-default-transformations-per-request
* Overriding the Default Transformations}
* - **cache** `{boolean|Cache}` If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
@@ -736,24 +750,23 @@ function $HttpProvider() {
</example>
*/
function $http(requestConfig) {
var config = {
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse
};
var headers = mergeHeaders(requestConfig);
if (!angular.isObject(requestConfig)) {
throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
}
extend(config, requestConfig);
config.headers = headers;
var config = extend({
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse
}, requestConfig);
config.headers = mergeHeaders(requestConfig);
config.method = uppercase(config.method);
var serverRequest = function(config) {
headers = config.headers;
var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
var headers = config.headers;
var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
// strip content-type if data is undefined
if (isUndefined(reqData)) {
@@ -769,7 +782,7 @@ function $HttpProvider() {
}
// send request
return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
return sendReq(config, reqData).then(transformResponse, transformResponse);
};
var chain = [serverRequest, undefined];
@@ -814,13 +827,30 @@ function $HttpProvider() {
if (!response.data) {
resp.data = response.data;
} else {
resp.data = transformData(response.data, response.headers, config.transformResponse);
resp.data = transformData(response.data, response.headers, response.status, config.transformResponse);
}
return (isSuccess(response.status))
? resp
: $q.reject(resp);
}
function executeHeaderFns(headers) {
var headerContent, processedHeaders = {};
forEach(headers, function(headerFn, header) {
if (isFunction(headerFn)) {
headerContent = headerFn();
if (headerContent != null) {
processedHeaders[header] = headerContent;
}
} else {
processedHeaders[header] = headerFn;
}
});
return processedHeaders;
}
function mergeHeaders(config) {
var defHeaders = defaults.headers,
reqHeaders = extend({}, config.headers),
@@ -843,23 +873,7 @@ function $HttpProvider() {
}
// execute if header value is a function for merged headers
execHeaders(reqHeaders);
return reqHeaders;
function execHeaders(headers) {
var headerContent;
forEach(headers, function(headerFn, header) {
if (isFunction(headerFn)) {
headerContent = headerFn();
if (headerContent != null) {
headers[header] = headerContent;
} else {
delete headers[header];
}
}
});
}
return executeHeaderFns(reqHeaders);
}
}
@@ -1002,11 +1016,12 @@ function $HttpProvider() {
* !!! ACCESSES CLOSURE VARS:
* $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
*/
function sendReq(config, reqData, reqHeaders) {
function sendReq(config, reqData) {
var deferred = $q.defer(),
promise = deferred.promise,
cache,
cachedResp,
reqHeaders = config.headers,
url = buildUrl(config.url, config.params);
$http.pendingRequests.push(config);
@@ -1025,8 +1040,7 @@ function $HttpProvider() {
if (isDefined(cachedResp)) {
if (isPromiseLike(cachedResp)) {
// cached request has already been sent, but there is no response yet
cachedResp.then(removePendingReq, removePendingReq);
return cachedResp;
cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
} else {
// serving from cache
if (isArray(cachedResp)) {
@@ -1104,6 +1118,9 @@ function $HttpProvider() {
});
}
function resolvePromiseWithResult(result) {
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
}
function removePendingReq() {
var idx = $http.pendingRequests.indexOf(config);
+3 -1
View File
@@ -126,7 +126,9 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
function completeRequest(callback, status, response, headersString, statusText) {
// cancel timeout and subsequent timeout promise resolution
timeoutId && $browserDefer.cancel(timeoutId);
if (timeoutId !== undefined) {
$browserDefer.cancel(timeoutId);
}
jsonpDone = xhr = null;
callback(status, response, headersString, statusText);
+24 -24
View File
@@ -57,33 +57,33 @@ function $IntervalProvider() {
* // Don't start a new fight if we are already fighting
* if ( angular.isDefined(stop) ) return;
*
* stop = $interval(function() {
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
* $scope.blood_1 = $scope.blood_1 - 3;
* $scope.blood_2 = $scope.blood_2 - 4;
* } else {
* $scope.stopFight();
* stop = $interval(function() {
* if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
* $scope.blood_1 = $scope.blood_1 - 3;
* $scope.blood_2 = $scope.blood_2 - 4;
* } else {
* $scope.stopFight();
* }
* }, 100);
* };
*
* $scope.stopFight = function() {
* if (angular.isDefined(stop)) {
* $interval.cancel(stop);
* stop = undefined;
* }
* }, 100);
* };
* };
*
* $scope.stopFight = function() {
* if (angular.isDefined(stop)) {
* $interval.cancel(stop);
* stop = undefined;
* }
* };
* $scope.resetFight = function() {
* $scope.blood_1 = 100;
* $scope.blood_2 = 120;
* };
*
* $scope.resetFight = function() {
* $scope.blood_1 = 100;
* $scope.blood_2 = 120;
* };
*
* $scope.$on('$destroy', function() {
* // Make sure that the interval is destroyed too
* $scope.stopFight();
* });
* }])
* $scope.$on('$destroy', function() {
* // Make sure that the interval is destroyed too
* $scope.stopFight();
* });
* }])
* // Register the 'myCurrentTime' directive factory method.
* // We inject $interval and dateFilter service since the factory method is DI.
* .directive('myCurrentTime', ['$interval', 'dateFilter',
+28 -14
View File
@@ -68,6 +68,10 @@ function stripHash(url) {
return index == -1 ? url : url.substr(0, index);
}
function trimEmptyHash(url) {
return url.replace(/(#.+)|#$/, '$1');
}
function stripFile(url) {
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
@@ -179,16 +183,25 @@ function LocationHashbangUrl(appBase, hashPrefix) {
*/
this.$$parse = function(url) {
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
var withoutHashUrl = withoutBaseUrl.charAt(0) == '#'
? beginsWith(hashPrefix, withoutBaseUrl)
: (this.$$html5)
? withoutBaseUrl
: '';
var withoutHashUrl;
if (!isString(withoutHashUrl)) {
throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
hashPrefix);
if (withoutBaseUrl.charAt(0) === '#') {
// The rest of the url starts with a hash so we have
// got either a hashbang path or a plain hash fragment
withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl);
if (isUndefined(withoutHashUrl)) {
// There was no hashbang prefix so we just have a hash fragment
withoutHashUrl = withoutBaseUrl;
}
} else {
// There was no hashbang path nor hash fragment:
// If we are in HTML5 mode we use what is left as the path;
// Otherwise we ignore what is left
withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
}
parseAppUrl(withoutHashUrl, this);
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
@@ -551,7 +564,7 @@ var locationPrototype = {
*
*
* ```js
* // given url http://example.com/some/path?foo=bar&baz=xoxo#hashValue
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
* var hash = $location.hash();
* // => "hashValue"
* ```
@@ -775,8 +788,8 @@ function $LocationProvider() {
* @param {string=} oldState History state object that was before it was changed.
*/
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
function($rootScope, $browser, $sniffer, $rootElement) {
this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window',
function($rootScope, $browser, $sniffer, $rootElement, $window) {
var $location,
LocationMode,
baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to ''
@@ -858,7 +871,7 @@ function $LocationProvider() {
if ($location.absUrl() != $browser.url()) {
$rootScope.$apply();
// hack to work around FF6 bug 684208 when scenario runner clicks on links
window.angular['ff-684208-preventDefault'] = true;
$window.angular['ff-684208-preventDefault'] = true;
}
}
}
@@ -903,10 +916,11 @@ function $LocationProvider() {
// update browser
$rootScope.$watch(function $locationWatch() {
var oldUrl = $browser.url();
var oldUrl = trimEmptyHash($browser.url());
var newUrl = trimEmptyHash($location.absUrl());
var oldState = $browser.state();
var currentReplace = $location.$$replace;
var urlOrStateChanged = oldUrl !== $location.absUrl() ||
var urlOrStateChanged = oldUrl !== newUrl ||
($location.$$html5 && $sniffer.history && oldState !== $location.$$state);
if (initializing || urlOrStateChanged) {
+16 -15
View File
@@ -362,6 +362,8 @@ Parser.prototype = {
primary = this.arrayDeclaration();
} else if (this.expect('{')) {
primary = this.object();
} else if (this.peek().identifier && this.peek().text in CONSTANTS) {
primary = CONSTANTS[this.consume().text];
} else if (this.peek().identifier) {
primary = this.identifier();
} else if (this.peek().constant) {
@@ -464,7 +466,7 @@ Parser.prototype = {
id += this.consume().text + this.consume().text;
}
return CONSTANTS[id] || getterFn(id, this.options, this.text);
return getterFn(id, this.options, this.text);
},
constant: function() {
@@ -598,8 +600,8 @@ Parser.prototype = {
logicalAND: function() {
var left = this.equality();
var token;
if ((token = this.expect('&&'))) {
left = this.binaryFn(left, token.text, this.logicalAND(), true);
while ((token = this.expect('&&'))) {
left = this.binaryFn(left, token.text, this.equality(), true);
}
return left;
},
@@ -607,8 +609,8 @@ Parser.prototype = {
equality: function() {
var left = this.relational();
var token;
if ((token = this.expect('==','!=','===','!=='))) {
left = this.binaryFn(left, token.text, this.equality());
while ((token = this.expect('==','!=','===','!=='))) {
left = this.binaryFn(left, token.text, this.relational());
}
return left;
},
@@ -616,8 +618,8 @@ Parser.prototype = {
relational: function() {
var left = this.additive();
var token;
if ((token = this.expect('<', '>', '<=', '>='))) {
left = this.binaryFn(left, token.text, this.relational());
while ((token = this.expect('<', '>', '<=', '>='))) {
left = this.binaryFn(left, token.text, this.additive());
}
return left;
},
@@ -654,17 +656,16 @@ Parser.prototype = {
},
fieldAccess: function(object) {
var expression = this.text;
var field = this.consume().text;
var getter = getterFn(field, this.options, expression);
var getter = this.identifier();
return extend(function $parseFieldAccess(scope, locals, self) {
return getter(self || object(scope, locals));
var o = self || object(scope, locals);
return (o == null) ? undefined : getter(o);
}, {
assign: function(scope, value, locals) {
var o = object(scope, locals);
if (!o) object.assign(scope, o = {});
return setter(o, field, value, expression);
return getter.assign(o, value);
}
});
},
@@ -709,7 +710,7 @@ Parser.prototype = {
var args = argsFn.length ? [] : null;
return function $parseFunctionCall(scope, locals) {
var context = contextGetter ? contextGetter(scope, locals) : scope;
var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope;
var fn = fnGetter(scope, locals, context) || noop;
if (args) {
@@ -722,13 +723,13 @@ Parser.prototype = {
ensureSafeObject(context, expressionText);
ensureSafeFunction(fn, expressionText);
// IE stupidity! (IE doesn't have apply for some native functions)
// IE doesn't have apply for some native functions
var v = fn.apply
? fn.apply(context, args)
: fn(args[0], args[1], args[2], args[3], args[4]);
return ensureSafeObject(v, expressionText);
};
};
},
// This is used with json array declaration
+1 -3
View File
@@ -3,12 +3,10 @@
function $$RAFProvider() { //rAF
this.$get = ['$window', '$timeout', function($window, $timeout) {
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
$window.mozRequestAnimationFrame;
$window.webkitRequestAnimationFrame;
var cancelAnimationFrame = $window.cancelAnimationFrame ||
$window.webkitCancelAnimationFrame ||
$window.mozCancelAnimationFrame ||
$window.webkitCancelRequestAnimationFrame;
var rafSupported = !!requestAnimationFrame;
+3 -3
View File
@@ -7,9 +7,9 @@
* @description
* The root element of Angular application. This is either the element where {@link
* ng.directive:ngApp ngApp} was declared or the element passed into
* {@link angular.bootstrap}. The element represent the root element of application. It is also the
* location where the applications {@link auto.$injector $injector} service gets
* published, it can be retrieved using `$rootElement.injector()`.
* {@link angular.bootstrap}. The element represents the root element of application. It is also the
* location where the application's {@link auto.$injector $injector} service gets
* published, and can be retrieved using `$rootElement.injector()`.
*/
+4 -4
View File
@@ -105,7 +105,6 @@ function $RootScopeProvider() {
var child = parent.$new();
parent.salutation = "Hello";
child.name = "World";
expect(child.salutation).toEqual('Hello');
child.salutation = "Welcome";
@@ -743,7 +742,7 @@ function $RootScopeProvider() {
while (asyncQueue.length) {
try {
asyncTask = asyncQueue.shift();
asyncTask.scope.$eval(asyncTask.expression);
asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals);
} catch (e) {
$exceptionHandler(e);
}
@@ -958,8 +957,9 @@ 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.
*/
$evalAsync: function(expr) {
$evalAsync: function(expr, locals) {
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
if (!$rootScope.$$phase && !asyncQueue.length) {
@@ -970,7 +970,7 @@ function $RootScopeProvider() {
});
}
asyncQueue.push({scope: this, expression: expr});
asyncQueue.push({scope: this, expression: expr, locals: locals});
},
$$postDigest: function(fn) {
+3 -1
View File
@@ -67,7 +67,9 @@ function $SnifferProvider() {
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
// when cut operation is performed.
if (event == 'input' && msie == 9) return false;
// IE10+ implements 'input' event but it erroneously fires under various situations,
// e.g. when placeholder changes, or a form is focused.
if (event === 'input' && msie <= 11) return false;
if (isUndefined(eventSupport[event])) {
var divElm = document.createElement('div');
+6 -13
View File
@@ -28,14 +28,9 @@ function $TemplateRequestProvider() {
var transformResponse = $http.defaults && $http.defaults.transformResponse;
if (isArray(transformResponse)) {
var original = transformResponse;
transformResponse = [];
for (var i = 0; i < original.length; ++i) {
var transformer = original[i];
if (transformer !== defaultHttpResponseTransform) {
transformResponse.push(transformer);
}
}
transformResponse = transformResponse.filter(function(transformer) {
return transformer !== defaultHttpResponseTransform;
});
} else if (transformResponse === defaultHttpResponseTransform) {
transformResponse = null;
}
@@ -47,18 +42,16 @@ function $TemplateRequestProvider() {
return $http.get(tpl, httpOptions)
.then(function(response) {
var html = response.data;
self.totalPendingRequests--;
$templateCache.put(tpl, html);
return html;
return response.data;
}, handleError);
function handleError() {
function handleError(resp) {
self.totalPendingRequests--;
if (!ignoreRequestError) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
}
return $q.reject();
return $q.reject(resp);
}
}
+120 -119
View File
@@ -13,7 +13,7 @@
* # Usage
*
* To see animations in action, all that is required is to define the appropriate CSS classes
* or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are:
* or to register a JavaScript animation via the `myModule.animation()` function. The directives that support animation automatically are:
* `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
* by using the `$animate` service.
*
@@ -155,8 +155,8 @@
* ### Structural transition animations
*
* Structural transitions (such as enter, leave and move) will always apply a `0s none` transition
* value to force the browser into rendering the styles defined in the setup (.ng-enter, .ng-leave
* or .ng-move) class. This means that any active transition animations operating on the element
* value to force the browser into rendering the styles defined in the setup (`.ng-enter`, `.ng-leave`
* or `.ng-move`) class. This means that any active transition animations operating on the element
* will be cut off to make way for the enter, leave or move animation.
*
* ### Class-based transition animations
@@ -473,11 +473,12 @@ angular.module('ngAnimate', ['ng'])
function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}
var $$jqLite;
$provide.decorator('$animate',
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) {
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest', '$$jqLite',
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest, $$$jqLite) {
$$jqLite = $$$jqLite;
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// Wait until all directive and route-related templates are downloaded and
@@ -871,22 +872,22 @@ angular.module('ngAnimate', ['ng'])
*
* Below is a breakdown of each step that occurs during the `animate` animation:
*
* | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. $animate.animate(...) is called | class="my-animation" |
* | 2. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
* | 3. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
* | 4. the className class value is added to the element | class="my-animation ng-animate className" |
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate className" |
* | 6. $animate blocks all CSS transitions on the element to ensure the .className class styling is applied right away| class="my-animation ng-animate className" |
* | 7. $animate applies the provided collection of `from` CSS styles to the element | class="my-animation ng-animate className" |
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate className" |
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate className" |
* | 10. the className-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate className className-active" |
* | 11. $animate applies the collection of `to` CSS styles to the element which are then handled by the transition | class="my-animation ng-animate className className-active" |
* | 12. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate className className-active" |
* | 13. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
* | 14. The returned promise is resolved. | class="my-animation" |
* | Animation Step | What the element class attribute looks like |
* |-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|
* | 1. `$animate.animate(...)` is called | `class="my-animation"` |
* | 2. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 3. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 4. the `className` class value is added to the element | `class="my-animation ng-animate className"` |
* | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate className"` |
* | 6. `$animate` blocks all CSS transitions on the element to ensure the `.className` class styling is applied right away| `class="my-animation ng-animate className"` |
* | 7. `$animate` applies the provided collection of `from` CSS styles to the element | `class="my-animation ng-animate className"` |
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate className"` |
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate className"` |
* | 10. the `className-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate className className-active"` |
* | 11. `$animate` applies the collection of `to` CSS styles to the element which are then handled by the transition | `class="my-animation ng-animate className className-active"` |
* | 12. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate className className-active"` |
* | 13. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 14. The returned promise is resolved. | `class="my-animation"` |
*
* @param {DOMElement} element the element that will be the focus of the enter animation
* @param {object} from a collection of CSS styles that will be applied to the element at the start of the animation
@@ -917,21 +918,21 @@ angular.module('ngAnimate', ['ng'])
*
* Below is a breakdown of each step that occurs during enter animation:
*
* | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
* | 1. $animate.enter(...) is called | class="my-animation" |
* | 2. element is inserted into the parentElement element or beside the afterElement element | class="my-animation" |
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
* | 5. the .ng-enter class is added to the element | class="my-animation ng-animate ng-enter" |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-enter" |
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-enter class styling is applied right away | class="my-animation ng-animate ng-enter" |
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-enter" |
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-enter" |
* | 10. the .ng-enter-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-enter ng-enter-active" |
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-enter ng-enter-active" |
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
* | 13. The returned promise is resolved. | class="my-animation" |
* | Animation Step | What the element class attribute looks like |
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. `$animate.enter(...)` is called | `class="my-animation"` |
* | 2. element is inserted into the `parentElement` element or beside the `afterElement` element | `class="my-animation"` |
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 5. the `.ng-enter` class is added to the element | `class="my-animation ng-animate ng-enter"` |
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-enter"` |
* | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-enter` class styling is applied right away | `class="my-animation ng-animate ng-enter"` |
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-enter"` |
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-enter"` |
* | 10. the `.ng-enter-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
* | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-enter ng-enter-active"` |
* | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 13. The returned promise is resolved. | `class="my-animation"` |
*
* @param {DOMElement} element the element that will be the focus of the enter animation
* @param {DOMElement} parentElement the parent element of the element that will be the focus of the enter animation
@@ -963,21 +964,21 @@ angular.module('ngAnimate', ['ng'])
*
* Below is a breakdown of each step that occurs during leave animation:
*
* | Animation Step | What the element class attribute looks like |
* |-------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
* | 1. $animate.leave(...) is called | class="my-animation" |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
* | 4. the .ng-leave class is added to the element | class="my-animation ng-animate ng-leave" |
* | 5. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-leave" |
* | 6. $animate blocks all CSS transitions on the element to ensure the .ng-leave class styling is applied right away | class="my-animation ng-animate ng-leave" |
* | 7. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-leave" |
* | 8. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-leave" |
* | 9. the .ng-leave-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-leave ng-leave-active" |
* | 10. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-leave ng-leave-active" |
* | 11. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
* | 12. The element is removed from the DOM | ... |
* | 13. The returned promise is resolved. | ... |
* | Animation Step | What the element class attribute looks like |
* |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|
* | 1. `$animate.leave(...)` is called | `class="my-animation"` |
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 4. the `.ng-leave` class is added to the element | `class="my-animation ng-animate ng-leave"` |
* | 5. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-leave"` |
* | 6. `$animate` blocks all CSS transitions on the element to ensure the `.ng-leave` class styling is applied right away | `class="my-animation ng-animate ng-leave"` |
* | 7. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-leave"` |
* | 8. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-leave"` |
* | 9. the `.ng-leave-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
* | 10. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-leave ng-leave-active"` |
* | 11. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 12. The element is removed from the DOM | ... |
* | 13. The returned promise is resolved. | ... |
*
* @param {DOMElement} element the element that will be the focus of the leave animation
* @param {object=} options an optional collection of styles that will be picked up by the CSS transition/animation
@@ -1008,21 +1009,21 @@ angular.module('ngAnimate', ['ng'])
*
* Below is a breakdown of each step that occurs during move animation:
*
* | Animation Step | What the element class attribute looks like |
* |------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
* | 1. $animate.move(...) is called | class="my-animation" |
* | 2. element is moved into the parentElement element or beside the afterElement element | class="my-animation" |
* | 3. $animate waits for the next digest to start the animation | class="my-animation ng-animate" |
* | 4. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
* | 5. the .ng-move class is added to the element | class="my-animation ng-animate ng-move" |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate ng-move" |
* | 7. $animate blocks all CSS transitions on the element to ensure the .ng-move class styling is applied right away | class="my-animation ng-animate ng-move" |
* | 8. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate ng-move" |
* | 9. $animate removes the CSS transition block placed on the element | class="my-animation ng-animate ng-move" |
* | 10. the .ng-move-active class is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-move ng-move-active" |
* | 11. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate ng-move ng-move-active" |
* | 12. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
* | 13. The returned promise is resolved. | class="my-animation" |
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
* | 1. `$animate.move(...)` is called | `class="my-animation"` |
* | 2. element is moved into the parentElement element or beside the afterElement element | `class="my-animation"` |
* | 3. `$animate` waits for the next digest to start the animation | `class="my-animation ng-animate"` |
* | 4. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 5. the `.ng-move` class is added to the element | `class="my-animation ng-animate ng-move"` |
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate ng-move"` |
* | 7. `$animate` blocks all CSS transitions on the element to ensure the `.ng-move` class styling is applied right away | `class="my-animation ng-animate ng-move"` |
* | 8. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate ng-move"` |
* | 9. `$animate` removes the CSS transition block placed on the element | `class="my-animation ng-animate ng-move"` |
* | 10. the `.ng-move-active` class is added (this triggers the CSS transition/animation) | `class="my-animation ng-animate ng-move ng-move-active"` |
* | 11. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate ng-move ng-move-active"` |
* | 12. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 13. The returned promise is resolved. | `class="my-animation"` |
*
* @param {DOMElement} element the element that will be the focus of the move animation
* @param {DOMElement} parentElement the parentElement element of the element that will be the focus of the move animation
@@ -1056,18 +1057,18 @@ angular.module('ngAnimate', ['ng'])
*
* Below is a breakdown of each step that occurs during addClass animation:
*
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
* | 1. $animate.addClass(element, 'super') is called | class="my-animation" |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate" |
* | 3. the .super-add class is added to the element | class="my-animation ng-animate super-add" |
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate super-add" |
* | 5. the .super and .super-add-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate super super-add super-add-active" |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super super-add super-add-active" |
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super super-add super-add-active" |
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation super" |
* | 9. The super class is kept on the element | class="my-animation super" |
* | 10. The returned promise is resolved. | class="my-animation super" |
* | Animation Step | What the element class attribute looks like |
* |--------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
* | 1. `$animate.addClass(element, 'super')` is called | `class="my-animation"` |
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate"` |
* | 3. the `.super-add` class is added to the element | `class="my-animation ng-animate super-add"` |
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate super-add"` |
* | 5. the `.super` and `.super-add-active` classes are added (this triggers the CSS transition/animation) | `class="my-animation ng-animate super super-add super-add-active"` |
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super super-add super-add-active"` |
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super super-add super-add-active"` |
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation super"` |
* | 9. The super class is kept on the element | `class="my-animation super"` |
* | 10. The returned promise is resolved. | `class="my-animation super"` |
*
* @param {DOMElement} element the element that will be animated
* @param {string} className the CSS class that will be added to the element and then animated
@@ -1090,17 +1091,17 @@ angular.module('ngAnimate', ['ng'])
*
* Below is a breakdown of each step that occurs during removeClass animation:
*
* | Animation Step | What the element class attribute looks like |
* |------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|
* | 1. $animate.removeClass(element, 'super') is called | class="my-animation super" |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation super ng-animate" |
* | 3. the .super-remove class is added to the element | class="my-animation super ng-animate super-remove" |
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation super ng-animate super-remove" |
* | 5. the .super-remove-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate super-remove super-remove-active" |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate super-remove super-remove-active" |
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate super-remove super-remove-active" |
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation" |
* | 9. The returned promise is resolved. | class="my-animation" |
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
* | 1. `$animate.removeClass(element, 'super')` is called | `class="my-animation super"` |
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation super ng-animate"` |
* | 3. the `.super-remove` class is added to the element | `class="my-animation super ng-animate super-remove"` |
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation super ng-animate super-remove"` |
* | 5. the `.super-remove-active` classes are added and `.super` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate super-remove super-remove-active"` |
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate super-remove super-remove-active"` |
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate super-remove super-remove-active"` |
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation"` |
* | 9. The returned promise is resolved. | `class="my-animation"` |
*
*
* @param {DOMElement} element the element that will be animated
@@ -1118,19 +1119,19 @@ angular.module('ngAnimate', ['ng'])
* @name $animate#setClass
*
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
* Once complete, the `done()` callback will be fired (if provided).
*
* | Animation Step | What the element class attribute looks like |
* |--------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
* | 1. $animate.setClass(element, 'on', 'off') is called | class="my-animation off" |
* | 2. $animate runs the JavaScript-defined animations detected on the element | class="my-animation ng-animate off" |
* | 3. the .on-add and .off-remove classes are added to the element | class="my-animation ng-animate on-add off-remove off" |
* | 4. $animate waits for a single animation frame (this performs a reflow) | class="my-animation ng-animate on-add off-remove off" |
* | 5. the .on, .on-add-active and .off-remove-active classes are added and .off is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
* | 6. $animate scans the element styles to get the CSS transition/animation duration and delay | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
* | 7. $animate waits for the animation to complete (via events and timeout) | class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active" |
* | 8. The animation ends and all generated CSS classes are removed from the element | class="my-animation on" |
* | 9. The returned promise is resolved. | class="my-animation on" |
* | Animation Step | What the element class attribute looks like |
* |----------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|
* | 1. `$animate.setClass(element, 'on', 'off')` is called | `class="my-animation off"` |
* | 2. `$animate` runs the JavaScript-defined animations detected on the element | `class="my-animation ng-animate off"` |
* | 3. the `.on-add` and `.off-remove` classes are added to the element | `class="my-animation ng-animate on-add off-remove off"` |
* | 4. `$animate` waits for a single animation frame (this performs a reflow) | `class="my-animation ng-animate on-add off-remove off"` |
* | 5. the `.on`, `.on-add-active` and `.off-remove-active` classes are added and `.off` is removed (this triggers the CSS transition/animation) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
* | 6. `$animate` scans the element styles to get the CSS transition/animation duration and delay | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
* | 7. `$animate` waits for the animation to complete (via events and timeout) | `class="my-animation ng-animate on on-add on-add-active off-remove off-remove-active"` |
* | 8. The animation ends and all generated CSS classes are removed from the element | `class="my-animation on"` |
* | 9. The returned promise is resolved. | `class="my-animation on"` |
*
* @param {DOMElement} element the element which will have its CSS classes changed
* removed from it
@@ -1268,7 +1269,7 @@ angular.module('ngAnimate', ['ng'])
all animations call this shared animation triggering function internally.
The animationEvent variable refers to the JavaScript animation event that will be triggered
and the className value is the name of the animation that will be applied within the
CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
CSS code. Element, `parentElement` and `afterElement` are provided DOM elements for the animation
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, options, doneCallback) {
@@ -1380,10 +1381,10 @@ angular.module('ngAnimate', ['ng'])
//the ng-animate class does nothing, but it's here to allow for
//parent animations to find and cancel child animations when needed
element.addClass(NG_ANIMATE_CLASS_NAME);
$$jqLite.addClass(element, NG_ANIMATE_CLASS_NAME);
if (options && options.tempClasses) {
forEach(options.tempClasses, function(className) {
element.addClass(className);
$$jqLite.addClass(element, className);
});
}
@@ -1461,7 +1462,7 @@ angular.module('ngAnimate', ['ng'])
closeAnimation.hasBeenRun = true;
if (options && options.tempClasses) {
forEach(options.tempClasses, function(className) {
element.removeClass(className);
$$jqLite.removeClass(element, className);
});
}
@@ -1523,7 +1524,7 @@ angular.module('ngAnimate', ['ng'])
}
if (removeAnimations || !data.totalActive) {
element.removeClass(NG_ANIMATE_CLASS_NAME);
$$jqLite.removeClass(element, NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE);
}
}
@@ -1764,14 +1765,14 @@ angular.module('ngAnimate', ['ng'])
var staggerCacheKey = cacheKey + ' ' + staggerClassName;
var applyClasses = !lookupCache[staggerCacheKey];
applyClasses && element.addClass(staggerClassName);
applyClasses && $$jqLite.addClass(element, staggerClassName);
stagger = getElementAnimationDetails(element, staggerCacheKey);
applyClasses && element.removeClass(staggerClassName);
applyClasses && $$jqLite.removeClass(element, staggerClassName);
}
element.addClass(className);
$$jqLite.addClass(element, className);
var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
var timings = getElementAnimationDetails(element, eventCacheKey);
@@ -1779,7 +1780,7 @@ angular.module('ngAnimate', ['ng'])
var animationDuration = timings.animationDuration;
if (structural && transitionDuration === 0 && animationDuration === 0) {
element.removeClass(className);
$$jqLite.removeClass(element, className);
return false;
}
@@ -1851,7 +1852,7 @@ angular.module('ngAnimate', ['ng'])
}
if (!staggerTime) {
element.addClass(activeClassName);
$$jqLite.addClass(element, activeClassName);
if (elementData.blockTransition) {
blockTransitions(node, false);
}
@@ -1861,7 +1862,7 @@ angular.module('ngAnimate', ['ng'])
var timings = getElementAnimationDetails(element, eventCacheKey);
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
if (maxDuration === 0) {
element.removeClass(activeClassName);
$$jqLite.removeClass(element, activeClassName);
animateClose(element, className);
activeAnimationComplete();
return;
@@ -1896,7 +1897,7 @@ angular.module('ngAnimate', ['ng'])
var staggerTimeout;
if (staggerTime > 0) {
element.addClass(pendingClassName);
$$jqLite.addClass(element, pendingClassName);
staggerTimeout = $timeout(function() {
staggerTimeout = null;
@@ -1907,8 +1908,8 @@ angular.module('ngAnimate', ['ng'])
blockAnimations(node, false);
}
element.addClass(activeClassName);
element.removeClass(pendingClassName);
$$jqLite.addClass(element, activeClassName);
$$jqLite.removeClass(element, pendingClassName);
if (styles) {
if (timings.transitionDuration === 0) {
@@ -1935,8 +1936,8 @@ angular.module('ngAnimate', ['ng'])
// timeout done method.
function onEnd() {
element.off(css3AnimationEvents, onAnimationProgress);
element.removeClass(activeClassName);
element.removeClass(pendingClassName);
$$jqLite.removeClass(element, activeClassName);
$$jqLite.removeClass(element, pendingClassName);
if (staggerTimeout) {
$timeout.cancel(staggerTimeout);
}
@@ -2024,7 +2025,7 @@ angular.module('ngAnimate', ['ng'])
}
function animateClose(element, className) {
element.removeClass(className);
$$jqLite.removeClass(element, className);
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if (data) {
if (data.running) {
+38 -27
View File
@@ -26,7 +26,7 @@
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
* | {@link ng.directive:ngClick ngClick} | tabindex |
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event |
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
* | {@link module:ngMessages ngMessages} | aria-live |
*
@@ -82,7 +82,8 @@ function $AriaProvider() {
ariaInvalid: true,
ariaMultiline: true,
ariaValue: true,
tabindex: true
tabindex: true,
bindKeypress: true
};
/**
@@ -99,6 +100,7 @@ function $AriaProvider() {
* - **ariaMultiline** `{boolean}` Enables/disables aria-multiline tags
* - **ariaValue** `{boolean}` Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
* - **tabindex** `{boolean}` Enables/disables tabindex tags
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on ng-click
*
* @description
* Enables/disables various ARIA attributes
@@ -107,16 +109,9 @@ function $AriaProvider() {
config = angular.extend(config, newConfig);
};
function camelCase(input) {
return input.replace(/-./g, function(letter, pos) {
return letter[1].toUpperCase();
});
}
function watchExpr(attrName, ariaAttr, negate) {
var ariaCamelName = camelCase(ariaAttr);
return function(scope, elem, attr) {
var ariaCamelName = attr.$normalize(ariaAttr);
if (config[ariaCamelName] && !attr[ariaCamelName]) {
scope.$watch(attr[attrName], function(boolVal) {
if (negate) {
@@ -176,20 +171,13 @@ function $AriaProvider() {
this.$get = function() {
return {
config: function(key) {
return config[camelCase(key)];
return config[key];
},
$$watchExpr: watchExpr
};
};
}
var ngAriaTabindex = ['$aria', function($aria) {
return function(scope, elem, attr) {
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}
};
}];
ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return $aria.$$watchExpr('ngShow', 'aria-hidden', true);
@@ -199,8 +187,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}])
.directive('ngModel', ['$aria', function($aria) {
function shouldAttachAttr(attr, elem) {
return $aria.config(attr) && !elem.attr(attr);
function shouldAttachAttr(attr, normalizedAttr, elem) {
return $aria.config(normalizedAttr) && !elem.attr(attr);
}
function getShape(attr, elem) {
@@ -218,7 +206,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
require: '?ngModel',
link: function(scope, elem, attr, ngModel) {
var shape = getShape(attr, elem);
var needsTabIndex = shouldAttachAttr('tabindex', elem);
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
function ngAriaWatchModelValue() {
return ngModel.$modelValue;
@@ -246,7 +234,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
switch (shape) {
case 'radio':
case 'checkbox':
if (shouldAttachAttr('aria-checked', elem)) {
if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
getRadioReaction() : ngAriaCheckboxReaction);
}
@@ -267,7 +255,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}
break;
case 'multiline':
if (shouldAttachAttr('aria-multiline', elem)) {
if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
elem.attr('aria-multiline', true);
}
break;
@@ -277,7 +265,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
elem.attr('tabindex', 0);
}
if (ngModel.$validators.required && shouldAttachAttr('aria-required', elem)) {
if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
scope.$watch(function ngAriaRequiredWatch() {
return ngModel.$error.required;
}, function ngAriaRequiredReaction(newVal) {
@@ -285,7 +273,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
});
}
if (shouldAttachAttr('aria-invalid', elem)) {
if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
scope.$watch(function ngAriaInvalidWatch() {
return ngModel.$invalid;
}, function ngAriaInvalidReaction(newVal) {
@@ -309,5 +297,28 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}
};
})
.directive('ngClick', ngAriaTabindex)
.directive('ngDblclick', ngAriaTabindex);
.directive('ngClick',['$aria', function($aria) {
return {
restrict: 'A',
link: function(scope, elem, attr) {
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}
if ($aria.config('bindKeypress') && !elem.attr('ng-keypress')) {
elem.on('keypress', function(event) {
if (event.keyCode === 32 || event.keyCode === 13) {
scope.$eval(attr.ngClick);
}
});
}
}
};
}])
.directive('ngDblclick', ['$aria', function($aria) {
return function(scope, elem, attr) {
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}
};
}]);
+9 -7
View File
@@ -1112,7 +1112,7 @@ angular.mock.dump = function(object) {
```
*/
angular.mock.$HttpBackendProvider = function() {
this.$get = ['$rootScope', createHttpBackendMock];
this.$get = ['$rootScope', '$timeout', createHttpBackendMock];
};
/**
@@ -1129,7 +1129,7 @@ angular.mock.$HttpBackendProvider = function() {
* @param {Object=} $browser Auto-flushing enabled if specified
* @return {Object} Instance of $httpBackend mock
*/
function createHttpBackendMock($rootScope, $delegate, $browser) {
function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var definitions = [],
expectations = [],
responses = [],
@@ -1142,7 +1142,7 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
return function() {
return angular.isNumber(status)
? [status, data, headers, statusText]
: [200, status, data];
: [200, status, data, headers];
};
}
@@ -1159,7 +1159,9 @@ function createHttpBackendMock($rootScope, $delegate, $browser) {
}
function wrapResponse(wrapped) {
if (!$browser && timeout && timeout.then) timeout.then(handleTimeout);
if (!$browser && timeout) {
timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout);
}
return handleResponse;
@@ -2033,7 +2035,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
*/
angular.mock.e2e = {};
angular.mock.e2e.$httpBackendDecorator =
['$rootScope', '$delegate', '$browser', createHttpBackendMock];
['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock];
/**
@@ -2047,7 +2049,7 @@ angular.mock.e2e.$httpBackendDecorator =
*
* In addition to all the regular `Scope` methods, the following helper methods are available:
*/
angular.mock.$RootScopeDecorator = function($delegate) {
angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
var $rootScopePrototype = Object.getPrototypeOf($delegate);
@@ -2119,7 +2121,7 @@ angular.mock.$RootScopeDecorator = function($delegate) {
return count;
}
};
}];
if (window.jasmine || window.mocha) {
+1 -1
View File
@@ -111,7 +111,7 @@ function shallowClearAndCopy(src, dst) {
* example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam`
* will be `data.someProp`.
*
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend
* @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
* the default set of resource actions. The declaration should be created in the format of {@link
* ng.$http#usage $http.config}:
*
-1
View File
@@ -79,7 +79,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
.view-animate-container {
position:relative;
height:100px!important;
position:relative;
background:white;
border:1px solid black;
height:40px;
+1 -1
View File
@@ -175,7 +175,7 @@ function $RouteProvider() {
* @description
*
* A boolean property indicating if routes defined
* using this provider should be matched using a case sensitive
* using this provider should be matched using a case insensitive
* algorithm. Defaults to `false`.
*/
this.caseInsensitiveMatch = false;
+6 -4
View File
@@ -104,7 +104,7 @@
*/
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,
MAILTO_REGEXP = /^mailto:/;
return function(text, target) {
@@ -117,8 +117,10 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
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;
// if we did not match ftp/http/www/mailto then assume mailto
if (!match[2] && !match[4]) {
url = (match[3] ? 'http://' : 'mailto:') + url;
}
i = match.index;
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
@@ -142,7 +144,7 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
'" ');
}
html.push('href="',
url.replace('"', '&quot;'),
url.replace(/"/g, '&quot;'),
'">');
addText(text);
html.push('</a>');
+1 -1
View File
@@ -199,7 +199,7 @@ angular.scenario.dsl('binding', function() {
*/
angular.scenario.dsl('input', function() {
var chain = {};
var supportInputEvent = 'oninput' in document.createElement('div') && msie != 9;
var supportInputEvent = 'oninput' in document.createElement('div') && !(msie && msie <= 11);
chain.enter = function(value, event) {
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'",
+10 -2
View File
@@ -1191,9 +1191,17 @@ describe('angular', function() {
it('should format objects pretty', function() {
expect(toJson({a: 1, b: 2}, true)).
toBeOneOf('{\n "a": 1,\n "b": 2\n}', '{\n "a":1,\n "b":2\n}');
toBe('{\n "a": 1,\n "b": 2\n}');
expect(toJson({a: {b: 2}}, true)).
toBeOneOf('{\n "a": {\n "b": 2\n }\n}', '{\n "a":{\n "b":2\n }\n}');
toBe('{\n "a": {\n "b": 2\n }\n}');
expect(toJson({a: 1, b: 2}, false)).
toBe('{"a":1,"b":2}');
expect(toJson({a: 1, b: 2}, 0)).
toBe('{"a":1,"b":2}');
expect(toJson({a: 1, b: 2}, 1)).
toBe('{\n "a": 1,\n "b": 2\n}');
expect(toJson({a: 1, b: 2}, {})).
toBe('{\n "a": 1,\n "b": 2\n}');
});
+19 -1
View File
@@ -4,12 +4,14 @@ describe('injector', function() {
var providers;
var injector;
var providerInjector;
var controllerProvider;
beforeEach(module(function($provide, $injector) {
beforeEach(module(function($provide, $injector, $controllerProvider) {
providers = function(name, factory, annotations) {
$provide.factory(name, extend(factory, annotations || {}));
};
providerInjector = $injector;
controllerProvider = $controllerProvider;
}));
beforeEach(inject(function($injector) {
injector = $injector;
@@ -74,6 +76,22 @@ describe('injector', function() {
});
it('should provide the caller name if given', function(done) {
expect(function() {
injector.get('idontexist', 'callerName');
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist <- callerName");
});
it('should provide the caller name for controllers', function(done) {
controllerProvider.register('myCtrl', function(idontexist) {});
var $controller = injector.get('$controller');
expect(function() {
$controller('myCtrl', {$scope: {}});
}).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist <- myCtrl");
});
it('should not corrupt the cache when an object fails to get instantiated', function() {
expect(function() {
injector.get('idontexist');
+21
View File
@@ -1,5 +1,7 @@
'use strict';
/* global getHash:true, stripHash:true */
var historyEntriesLength;
var sniffer = {};
@@ -51,6 +53,12 @@ function MockWindow(options) {
mockWindow.history.state = null;
historyEntriesLength++;
},
get hash() {
return getHash(locationHref);
},
set hash(value) {
locationHref = stripHash(locationHref) + '#' + value;
},
replace: function(url) {
locationHref = url;
mockWindow.history.state = null;
@@ -550,6 +558,17 @@ describe('browser', function() {
expect(locationReplace).not.toHaveBeenCalled();
});
it("should retain the # character when the only change is clearing the hash fragment, to prevent page reload", function() {
sniffer.history = true;
browser.url('http://server/#123');
expect(fakeWindow.location.href).toEqual('http://server/#123');
browser.url('http://server/');
expect(fakeWindow.location.href).toEqual('http://server/#');
});
it('should use location.replace when history.replaceState not available', function() {
sniffer.history = false;
browser.url('http://new.org', true);
@@ -561,6 +580,7 @@ describe('browser', function() {
expect(fakeWindow.location.href).toEqual('http://server/');
});
it('should use location.replace and not use replaceState when the url only changed in the hash fragment to please IE10/11', function() {
sniffer.history = true;
browser.url('http://server/#123', true);
@@ -572,6 +592,7 @@ describe('browser', function() {
expect(fakeWindow.location.href).toEqual('http://server/');
});
it('should return $browser to allow chaining', function() {
expect(browser.url('http://any.com')).toBe(browser);
});
+36 -2
View File
@@ -6542,16 +6542,21 @@ describe('$compile', function() {
expect(element.attr('href')).toBe('test/test');
}));
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
it('should work if they are prefixed with x- or data- and different prefixes', inject(function($compile, $rootScope) {
$rootScope.name = "Misko";
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}"></span>')($rootScope);
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}" ' +
'x_ng-attr-test5="{{name}}" data:ng-attr-test6="{{name}}"></span>')($rootScope);
expect(element.attr('test2')).toBeUndefined();
expect(element.attr('test3')).toBeUndefined();
expect(element.attr('test4')).toBeUndefined();
expect(element.attr('test5')).toBeUndefined();
expect(element.attr('test6')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('test2')).toBe('Misko');
expect(element.attr('test3')).toBe('Misko');
expect(element.attr('test4')).toBe('Misko');
expect(element.attr('test5')).toBe('Misko');
expect(element.attr('test6')).toBe('Misko');
}));
describe('when an attribute has a dash-separated name', function() {
@@ -6619,6 +6624,35 @@ describe('$compile', function() {
});
describe('when an attribute has an underscore-separated name', function() {
it('should work with different prefixes', inject(function($compile, $rootScope) {
$rootScope.dimensions = "0 0 0 0";
element = $compile('<svg ng:attr:view_box="{{dimensions}}"></svg>')($rootScope);
expect(element.attr('viewBox')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('viewBox')).toBe('0 0 0 0');
}));
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
$rootScope.dimensions = "0 0 0 0";
$rootScope.number = 0.42;
$rootScope.scale = 1;
element = $compile('<svg data-ng-attr-view_box="{{dimensions}}">' +
'<filter x-ng-attr-filter_units="{{number}}">' +
'<feDiffuseLighting data-ng:attr_surface_scale="{{scale}}">' +
'</feDiffuseLighting>' +
'<feSpecularLighting x-ng:attr_surface_scale="{{scale}}">' +
'</feSpecularLighting></filter></svg>')($rootScope);
expect(element.attr('viewBox')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('viewBox')).toBe('0 0 0 0');
expect(element.find('filter').attr('filterUnits')).toBe('0.42');
expect(element.find('feDiffuseLighting').attr('surfaceScale')).toBe('1');
expect(element.find('feSpecularLighting').attr('surfaceScale')).toBe('1');
}));
});
describe('multi-element directive', function() {
it('should group on link function', inject(function($compile, $rootScope) {
$rootScope.show = false;
+228 -11
View File
@@ -1066,6 +1066,56 @@ describe('NgModelController', function() {
dealoc(element);
}));
it('should always use the most recent $viewValue for validation', function() {
ctrl.$parsers.push(function(value) {
if (value && value.substr(-1) === 'b') {
value = 'a';
ctrl.$setViewValue(value);
ctrl.$render();
}
return value;
});
ctrl.$validators.mock = function(modelValue) {
return true;
};
spyOn(ctrl.$validators, 'mock').andCallThrough();
ctrl.$setViewValue('ab');
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a');
expect(ctrl.$validators.mock.calls.length).toEqual(2);
});
it('should validate even if the modelValue did not change', function() {
ctrl.$parsers.push(function(value) {
if (value && value.substr(-1) === 'b') {
value = 'a';
}
return value;
});
ctrl.$validators.mock = function(modelValue) {
return true;
};
spyOn(ctrl.$validators, 'mock').andCallThrough();
ctrl.$setViewValue('a');
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'a');
expect(ctrl.$validators.mock.calls.length).toEqual(1);
ctrl.$setViewValue('ab');
expect(ctrl.$validators.mock).toHaveBeenCalledWith('a', 'ab');
expect(ctrl.$validators.mock.calls.length).toEqual(2);
});
});
});
@@ -1549,22 +1599,170 @@ describe('input', function() {
expect(scope.name).toEqual('caitp');
});
it('should not dirty the model on an input event in response to a placeholder change', inject(function($sniffer) {
if (msie && $sniffer.hasEvent('input')) {
compileInput('<input type="text" ng-model="name" name="name" />');
inputElm.attr('placeholder', 'Test');
browserTrigger(inputElm, 'input');
describe("IE placeholder input events", function() {
//IE fires an input event whenever a placeholder visually changes, essentially treating it as a value
//Events:
// placeholder attribute change: *input*
// focus (which visually removes the placeholder value): focusin focus *input*
// blur (which visually creates the placeholder value): focusout *input* blur
//However none of these occur if the placeholder is not visible at the time of the event.
//These tests try simulate various scenerios which do/do-not fire the extra input event
it('should not dirty the model on an input event in response to a placeholder change', function() {
compileInput('<input type="text" placeholder="Test" attr-capture ng-model="unsetValue" name="name" />');
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe('Test');
expect(inputElm).toBePristine();
inputElm.attr('placeholder', 'Test Again');
browserTrigger(inputElm, 'input');
attrs.$set('placeholder', '');
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe('');
expect(inputElm).toBePristine();
attrs.$set('placeholder', 'Test Again');
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe('Test Again');
expect(inputElm).toBePristine();
}
}));
attrs.$set('placeholder', undefined);
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe(undefined);
expect(inputElm).toBePristine();
changeInputValueTo('foo');
expect(inputElm).toBeDirty();
});
it('should not dirty the model on an input event in response to a interpolated placeholder change', inject(function($rootScope) {
compileInput('<input type="text" placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
msie && browserTrigger(inputElm, 'input');
expect(inputElm).toBePristine();
$rootScope.ph = 1;
$rootScope.$digest();
msie && browserTrigger(inputElm, 'input');
expect(inputElm).toBePristine();
$rootScope.ph = "";
$rootScope.$digest();
msie && browserTrigger(inputElm, 'input');
expect(inputElm).toBePristine();
changeInputValueTo('foo');
expect(inputElm).toBeDirty();
}));
it('should dirty the model on an input event while in focus even if the placeholder changes', inject(function($rootScope) {
$rootScope.ph = 'Test';
compileInput('<input type="text" ng-attr-placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
expect(inputElm).toBePristine();
browserTrigger(inputElm, 'focusin');
browserTrigger(inputElm, 'focus');
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe('Test');
expect(inputElm).toBePristine();
$rootScope.ph = 'Test Again';
$rootScope.$digest();
expect(inputElm).toBePristine();
changeInputValueTo('foo');
expect(inputElm).toBeDirty();
}));
it('should not dirty the model on an input event in response to a ng-attr-placeholder change', inject(function($rootScope) {
compileInput('<input type="text" ng-attr-placeholder="{{ph}}" ng-model="unsetValue" name="name" />');
expect(inputElm).toBePristine();
$rootScope.ph = 1;
$rootScope.$digest();
msie && browserTrigger(inputElm, 'input');
expect(inputElm).toBePristine();
$rootScope.ph = "";
$rootScope.$digest();
msie && browserTrigger(inputElm, 'input');
expect(inputElm).toBePristine();
changeInputValueTo('foo');
expect(inputElm).toBeDirty();
}));
it('should not dirty the model on an input event in response to a focus', inject(function($sniffer) {
compileInput('<input type="text" placeholder="Test" ng-model="unsetValue" name="name" />');
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe('Test');
expect(inputElm).toBePristine();
browserTrigger(inputElm, 'focusin');
browserTrigger(inputElm, 'focus');
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe('Test');
expect(inputElm).toBePristine();
changeInputValueTo('foo');
expect(inputElm).toBeDirty();
}));
it('should not dirty the model on an input event in response to a blur', inject(function($sniffer) {
compileInput('<input type="text" placeholder="Test" ng-model="unsetValue" name="name" />');
msie && browserTrigger(inputElm, 'input');
expect(inputElm.attr('placeholder')).toBe('Test');
expect(inputElm).toBePristine();
browserTrigger(inputElm, 'focusin');
browserTrigger(inputElm, 'focus');
msie && browserTrigger(inputElm, 'input');
expect(inputElm).toBePristine();
browserTrigger(inputElm, 'focusout');
msie && browserTrigger(inputElm, 'input');
browserTrigger(inputElm, 'blur');
expect(inputElm).toBePristine();
changeInputValueTo('foo');
expect(inputElm).toBeDirty();
}));
it('should dirty the model on an input event if there is a placeholder and value', inject(function($rootScope) {
$rootScope.name = 'foo';
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
expect(inputElm.val()).toBe($rootScope.name);
expect(inputElm).toBePristine();
changeInputValueTo('bar');
expect(inputElm).toBeDirty();
}));
it('should dirty the model on an input event if there is a placeholder and value after focusing', inject(function($rootScope) {
$rootScope.name = 'foo';
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
expect(inputElm.val()).toBe($rootScope.name);
expect(inputElm).toBePristine();
browserTrigger(inputElm, 'focusin');
browserTrigger(inputElm, 'focus');
changeInputValueTo('bar');
expect(inputElm).toBeDirty();
}));
it('should dirty the model on an input event if there is a placeholder and value after bluring', inject(function($rootScope) {
$rootScope.name = 'foo';
compileInput('<input type="text" placeholder="Test" ng-model="name" value="init" name="name" />');
expect(inputElm.val()).toBe($rootScope.name);
expect(inputElm).toBePristine();
browserTrigger(inputElm, 'focusin');
browserTrigger(inputElm, 'focus');
expect(inputElm).toBePristine();
browserTrigger(inputElm, 'focusout');
browserTrigger(inputElm, 'blur');
changeInputValueTo('bar');
expect(inputElm).toBeDirty();
}));
});
it('should interpolate input names', function() {
@@ -1656,7 +1854,7 @@ describe('input', function() {
}
});
describe('"paste" and "cut" events', function() {
describe('"keydown", "paste" and "cut" events', function() {
beforeEach(function() {
// Force browser to report a lack of an 'input' event
$sniffer.hasEvent = function(eventName) {
@@ -1664,9 +1862,13 @@ describe('input', function() {
};
});
it('should update the model on "paste" event', function() {
it('should update the model on "paste" event if the input value changes', function() {
compileInput('<input type="text" ng-model="name" name="alias" ng-change="change()" />');
browserTrigger(inputElm, 'keydown');
$browser.defer.flush();
expect(inputElm).toBePristine();
inputElm.val('mark');
browserTrigger(inputElm, 'paste');
$browser.defer.flush();
@@ -1682,6 +1884,21 @@ describe('input', function() {
expect(scope.name).toEqual('john');
});
it('should cancel the delayed dirty if a change occurs', function() {
compileInput('<input type="text" ng-model="name" />');
var ctrl = inputElm.controller('ngModel');
browserTrigger(inputElm, 'keydown', {target: inputElm[0]});
inputElm.val('f');
browserTrigger(inputElm, 'change');
expect(inputElm).toBeDirty();
ctrl.$setPristine();
scope.$apply();
$browser.defer.flush();
expect(inputElm).toBePristine();
});
});
+10
View File
@@ -146,6 +146,16 @@ describe('ngRepeat', function() {
expect(element.text()).toEqual('misko:swe|shyam:set|');
});
it('should iterate over on object/map where (key,value) contains whitespaces', function() {
element = $compile(
'<ul>' +
'<li ng-repeat="( key , value ) in items">{{key}}:{{value}}|</li>' +
'</ul>')(scope);
scope.items = {me:'swe', you:'set'};
scope.$digest();
expect(element.text()).toEqual('me:swe|you:set|');
});
it('should iterate over an object/map with identical values', function() {
element = $compile(
'<ul>' +
+207
View File
@@ -40,6 +40,23 @@ describe('select', function() {
return equals(expectedValues, actualValues);
},
toEqualSelectWithOptions: function(expected) {
var actualValues = {};
var optionGroup;
forEach(this.actual.find('option'), function(option) {
optionGroup = option.parentNode.label || '';
actualValues[optionGroup] = actualValues[optionGroup] || [];
actualValues[optionGroup].push(option.label);
});
this.message = function() {
return 'Expected ' + toJson(actualValues) + ' to equal ' + toJson(expected) + '.';
};
return equals(expected, actualValues);
},
toEqualOption: function(value, text, label) {
var errors = [];
if (this.actual.attr('value') !== value) {
@@ -233,6 +250,31 @@ describe('select', function() {
expect(scope.robot).toBe('');
});
it('should not be set when an option is selected and options are set asynchronously',
inject(function($timeout) {
compile('<select ng-model="model" ng-options="opt.id as opt.label for opt in options">' +
'</select>');
scope.$apply(function() {
scope.model = 0;
});
$timeout(function() {
scope.options = [
{id: 0, label: 'x'},
{id: 1, label: 'y'}
];
}, 0);
$timeout.flush();
var options = element.find('option');
expect(options.length).toEqual(2);
expect(options.eq(0)).toEqualOption('0', 'x');
expect(options.eq(1)).toEqualOption('1', 'y');
})
);
describe('interactions with repeated options', function() {
@@ -447,6 +489,7 @@ describe('select', function() {
expect(selectCtrl.hasOption('r2d2')).toBe(true);
});
it('should return false for options popped via ngOptions', function() {
scope.robots = [
{value: 1, label: 'c3p0'},
@@ -467,6 +510,7 @@ describe('select', function() {
expect(selectCtrl.hasOption('r2d2')).toBe(false);
});
it('should return true for options added via ngOptions', function() {
scope.robots = [
{value: 2, label: 'r2d2'}
@@ -485,6 +529,169 @@ describe('select', function() {
expect(selectCtrl.hasOption('c3p0')).toBe(true);
expect(selectCtrl.hasOption('r2d2')).toBe(true);
});
it('should keep all the options when changing the model', function() {
compile('<select ng-model="mySelect" ng-options="o for o in [\'A\',\'B\',\'C\']"></select>');
var selectCtrl = element.controller('select');
scope.$apply(function() {
scope.mySelect = 'C';
});
expect(selectCtrl.hasOption('A')).toBe(true);
expect(selectCtrl.hasOption('B')).toBe(true);
expect(selectCtrl.hasOption('C')).toBe(true);
expect(element).toEqualSelectWithOptions({'': ['A', 'B', 'C']});
});
it('should be able to detect when elements move from a previous group', function() {
scope.values = [
{name: 'A'},
{name: 'B', group: 'first'},
{name: 'C', group: 'first'},
{name: 'D', group: 'first'},
{name: 'E', group: 'second'}
];
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
var selectCtrl = element.data().$selectController;
scope.$apply(function() {
scope.values[3] = {name: 'D', group: 'second'};
scope.values.shift();
});
expect(selectCtrl.hasOption('A')).toBe(false);
expect(selectCtrl.hasOption('B')).toBe(true);
expect(selectCtrl.hasOption('C')).toBe(true);
expect(selectCtrl.hasOption('D')).toBe(true);
expect(selectCtrl.hasOption('E')).toBe(true);
expect(element).toEqualSelectWithOptions({'': [''], 'first':['B', 'C'], 'second': ['D', 'E']});
});
it('should be able to detect when elements move from a following group', function() {
scope.values = [
{name: 'A'},
{name: 'B', group: 'first'},
{name: 'C', group: 'first'},
{name: 'D', group: 'second'},
{name: 'E', group: 'second'}
];
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
var selectCtrl = element.data().$selectController;
scope.$apply(function() {
scope.values[3].group = 'first';
scope.values.shift();
});
expect(selectCtrl.hasOption('A')).toBe(false);
expect(selectCtrl.hasOption('B')).toBe(true);
expect(selectCtrl.hasOption('C')).toBe(true);
expect(selectCtrl.hasOption('D')).toBe(true);
expect(selectCtrl.hasOption('E')).toBe(true);
expect(element).toEqualSelectWithOptions({'': [''], 'first':['B', 'C', 'D'], 'second': ['E']});
});
it('should be able to detect when an element is replaced with an element from a previous group', function() {
scope.values = [
{name: 'A'},
{name: 'B', group: 'first'},
{name: 'C', group: 'first'},
{name: 'D', group: 'first'},
{name: 'E', group: 'second'},
{name: 'F', group: 'second'}
];
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
var selectCtrl = element.data().$selectController;
scope.$apply(function() {
scope.values[3].group = 'second';
scope.values.pop();
});
expect(selectCtrl.hasOption('A')).toBe(true);
expect(selectCtrl.hasOption('B')).toBe(true);
expect(selectCtrl.hasOption('C')).toBe(true);
expect(selectCtrl.hasOption('D')).toBe(true);
expect(selectCtrl.hasOption('E')).toBe(true);
expect(selectCtrl.hasOption('F')).toBe(false);
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'C'], 'second': ['D', 'E']});
});
it('should be able to detect when element is replaced with an element from a following group', function() {
scope.values = [
{name: 'A'},
{name: 'B', group: 'first'},
{name: 'C', group: 'first'},
{name: 'D', group: 'second'},
{name: 'E', group: 'second'}
];
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
var selectCtrl = element.data().$selectController;
scope.$apply(function() {
scope.values[3].group = 'first';
scope.values.splice(2, 1);
});
expect(selectCtrl.hasOption('A')).toBe(true);
expect(selectCtrl.hasOption('B')).toBe(true);
expect(selectCtrl.hasOption('C')).toBe(false);
expect(selectCtrl.hasOption('D')).toBe(true);
expect(selectCtrl.hasOption('E')).toBe(true);
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'D'], 'second': ['E']});
});
it('should be able to detect when an element is removed', function() {
scope.values = [
{name: 'A'},
{name: 'B', group: 'first'},
{name: 'C', group: 'first'},
{name: 'D', group: 'second'},
{name: 'E', group: 'second'}
];
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
var selectCtrl = element.data().$selectController;
scope.$apply(function() {
scope.values.splice(3, 1);
});
expect(selectCtrl.hasOption('A')).toBe(true);
expect(selectCtrl.hasOption('B')).toBe(true);
expect(selectCtrl.hasOption('C')).toBe(true);
expect(selectCtrl.hasOption('D')).toBe(false);
expect(selectCtrl.hasOption('E')).toBe(true);
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'C'], 'second': ['E']});
});
it('should be able to detect when a group is removed', function() {
scope.values = [
{name: 'A'},
{name: 'B', group: 'first'},
{name: 'C', group: 'first'},
{name: 'D', group: 'second'},
{name: 'E', group: 'second'}
];
compile('<select ng-model="mySelect" ng-options="item.name group by item.group for item in values"></select>');
var selectCtrl = element.data().$selectController;
scope.$apply(function() {
scope.values.splice(3, 2);
});
expect(selectCtrl.hasOption('A')).toBe(true);
expect(selectCtrl.hasOption('B')).toBe(true);
expect(selectCtrl.hasOption('C')).toBe(true);
expect(selectCtrl.hasOption('D')).toBe(false);
expect(selectCtrl.hasOption('E')).toBe(false);
expect(element).toEqualSelectWithOptions({'': ['', 'A'], 'first':['B', 'C']});
});
});
});
});
+242
View File
@@ -98,6 +98,19 @@ describe('Filter: filter', function() {
});
it('should support deep expression objects with multiple properties', function() {
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
{person: {name: 'Billy', email: 'me@billy.com'}},
{person: {name: 'Joan', email: 'joan@example.net'}},
{person: {name: 'John', email: 'john@example.com'}},
{person: {name: 'Rita', email: 'rita@example.com'}}];
var expr = {person: {name: 'Jo', email: '!example.com'}};
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)).toEqual([items[2]]);
});
it('should match any properties for given "$" property', function() {
var items = [{first: 'tom', last: 'hevery'},
{first: 'adam', last: 'hevery', alias: 'tom', done: false},
@@ -110,6 +123,43 @@ describe('Filter: filter', function() {
});
it('should match any properties in the nested object for given deep "$" property', function() {
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
{person: {name: 'Billy', email: 'me@billy.com'}},
{person: {name: 'Joan', email: 'joan@example.net'}},
{person: {name: 'John', email: 'john@example.com'}},
{person: {name: 'Rita', email: 'rita@example.com'}}];
var expr = {person: {$: 'net'}};
expect(filter(items, expr).length).toBe(2);
expect(filter(items, expr)).toEqual([items[0], items[2]]);
});
it('should respect the depth level of a "$" property', function() {
var items = [{person: {name: 'Annet', email: 'annet@example.com'}},
{person: {name: 'Billy', email: 'me@billy.com'}},
{person: {name: 'Joan', email: {home: 'me@joan.com', work: 'joan@example.net'}}}];
var expr = {person: {$: 'net'}};
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)).toEqual([items[0]]);
});
it('should respect the nesting level of "$"', function() {
var items = [{supervisor: 'me', person: {name: 'Annet', email: 'annet@example.com'}},
{supervisor: 'me', person: {name: 'Billy', email: 'me@billy.com'}},
{supervisor: 'me', person: {name: 'Joan', email: 'joan@example.net'}},
{supervisor: 'me', person: {name: 'John', email: 'john@example.com'}},
{supervisor: 'me', person: {name: 'Rita', email: 'rita@example.com'}}];
var expr = {$: {$: 'me'}};
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)).toEqual([items[1]]);
});
it('should support boolean properties', function() {
var items = [{name: 'tom', current: true},
{name: 'demi', current: false},
@@ -129,8 +179,159 @@ describe('Filter: filter', function() {
expect(filter(items, '!isk')[0]).toEqual(items[1]);
});
it('should ignore function properties in items', function() {
// Own function properties
var items = [
{text: 'hello', func: noop},
{text: 'goodbye'},
{text: 'kittens'},
{text: 'puppies'}
];
var expr = {text: 'hello'};
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)[0]).toBe(items[0]);
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr, true)[0]).toBe(items[0]);
// Inherited function proprties
function Item(text) {
this.text = text;
}
Item.prototype.func = noop;
items = [
new Item('hello'),
new Item('goodbye'),
new Item('kittens'),
new Item('puppies')
];
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)[0]).toBe(items[0]);
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr, true)[0]).toBe(items[0]);
});
it('should ignore function properties in expression', function() {
// Own function properties
var items = [
{text: 'hello'},
{text: 'goodbye'},
{text: 'kittens'},
{text: 'puppies'}
];
var expr = {text: 'hello', func: noop};
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)[0]).toBe(items[0]);
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr, true)[0]).toBe(items[0]);
// Inherited function proprties
function Expr(text) {
this.text = text;
}
Expr.prototype.func = noop;
expr = new Expr('hello');
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)[0]).toBe(items[0]);
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr, true)[0]).toBe(items[0]);
});
it('should consider inherited properties in items', function() {
function Item(text) {
this.text = text;
}
Item.prototype.doubleL = 'maybe';
var items = [
new Item('hello'),
new Item('goodbye'),
new Item('kittens'),
new Item('puppies')
];
var expr = {text: 'hello', doubleL: 'perhaps'};
expect(filter(items, expr).length).toBe(0);
expect(filter(items, expr, true).length).toBe(0);
expr = {text: 'hello', doubleL: 'maybe'};
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)[0]).toBe(items[0]);
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr, true)[0]).toBe(items[0]);
});
it('should consider inherited properties in expression', function() {
function Expr(text) {
this.text = text;
}
Expr.prototype.doubleL = true;
var items = [
{text: 'hello', doubleL: true},
{text: 'goodbye'},
{text: 'kittens'},
{text: 'puppies'}
];
var expr = new Expr('e');
expect(filter(items, expr).length).toBe(1);
expect(filter(items, expr)[0]).toBe(items[0]);
expr = new Expr('hello');
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr)[0]).toBe(items[0]);
});
it('should not be affected by `Object.prototype` when using a string expression', function() {
Object.prototype.someProp = 'oo';
var items = [
createMap(),
createMap(),
createMap(),
createMap()
];
items[0].someProp = 'hello';
items[1].someProp = 'goodbye';
items[2].someProp = 'kittens';
items[3].someProp = 'puppies';
// Affected by `Object.prototype`
expect(filter(items, {}).length).toBe(1);
expect(filter(items, {})[0]).toBe(items[1]);
expect(filter(items, {$: 'll'}).length).toBe(0);
// Not affected by `Object.prototype`
expect(filter(items, 'll').length).toBe(1);
expect(filter(items, 'll')[0]).toBe(items[0]);
delete Object.prototype.someProp;
});
describe('should support comparator', function() {
it('not consider `object === "[object Object]"` in non-strict comparison', function() {
var items = [{test: {}}];
var expr = '[object';
expect(filter(items, expr).length).toBe(0);
});
it('as equality when true', function() {
var items = ['misko', 'adam', 'adamson'];
var expr = 'adam';
@@ -177,5 +378,46 @@ describe('Filter: filter', function() {
expr = 10;
expect(filter(items, expr, comparator)).toEqual([items[2], items[3]]);
});
it('and use it correctly with deep expression objects', function() {
var items = [
{id: 0, details: {email: 'admin@example.com', role: 'admin'}},
{id: 1, details: {email: 'user1@example.com', role: 'user'}},
{id: 2, details: {email: 'user2@example.com', role: 'user'}}
];
var expr, comp;
expr = {details: {email: 'user@example.com', role: 'adm'}};
expect(filter(items, expr)).toEqual([]);
expr = {details: {email: 'admin@example.com', role: 'adm'}};
expect(filter(items, expr)).toEqual([items[0]]);
expr = {details: {email: 'admin@example.com', role: 'adm'}};
expect(filter(items, expr, true)).toEqual([]);
expr = {details: {email: 'admin@example.com', role: 'admin'}};
expect(filter(items, expr, true)).toEqual([items[0]]);
expr = {details: {email: 'user', role: 'us'}};
expect(filter(items, expr)).toEqual([items[1], items[2]]);
expr = {id: 0, details: {email: 'user', role: 'us'}};
expect(filter(items, expr)).toEqual([]);
expr = {id: 1, details: {email: 'user', role: 'us'}};
expect(filter(items, expr)).toEqual([items[1]]);
comp = function(actual, expected) {
return isString(actual) && isString(expected) && (actual.indexOf(expected) === 0);
};
expr = {details: {email: 'admin@example.com', role: 'min'}};
expect(filter(items, expr, comp)).toEqual([]);
expr = {details: {email: 'admin@example.com', role: 'adm'}};
expect(filter(items, expr, comp)).toEqual([items[0]]);
});
});
});
+40 -3
View File
@@ -85,8 +85,10 @@ describe('filters', function() {
});
it('should format numbers that round to zero as nonnegative', function() {
var num = formatNumber(-0.01, pattern, ',', '.', 1);
expect(num).toBe('0.0');
expect(formatNumber(-0.01, pattern, ',', '.', 1)).toBe('0.0');
expect(formatNumber(-1e-10, pattern, ',', '.', 1)).toBe('0.0');
expect(formatNumber(-0.0001, pattern, ',', '.', 3)).toBe('0.000');
expect(formatNumber(-0.0000001, pattern, ',', '.', 6)).toBe('0.000000');
});
});
@@ -195,10 +197,12 @@ describe('filters', function() {
expect(number(1e-50, 0)).toEqual('0');
expect(number(1e-6, 6)).toEqual('0.000001');
expect(number(1e-7, 6)).toEqual('0.000000');
expect(number(9e-7, 6)).toEqual('0.000001');
expect(number(-1e-50, 0)).toEqual('0');
expect(number(-1e-6, 6)).toEqual('-0.000001');
expect(number(-1e-7, 6)).toEqual('-0.000000');
expect(number(-1e-7, 6)).toEqual('0.000000');
expect(number(-1e-8, 9)).toEqual('-0.000000010');
});
});
@@ -206,6 +210,9 @@ describe('filters', function() {
it('should do basic filter', function() {
expect(filter('json')({a:"b"})).toEqual(toJson({a:"b"}, true));
});
it('should allow custom indentation', function() {
expect(filter('json')({a:"b"}, 4)).toEqual(toJson({a:"b"}, 4));
});
});
describe('lowercase', function() {
@@ -319,6 +326,36 @@ describe('filters', function() {
toEqual('2010-09-03T06:35:08-0530');
});
it('should correctly calculate week number', function() {
function formatWeek(dateToFormat) {
return date(new angular.mock.TzDate(+5, dateToFormat + 'T12:00:00.000Z'), 'ww (EEE)');
}
expect(formatWeek('2007-01-01')).toEqual('01 (Mon)');
expect(formatWeek('2007-12-31')).toEqual('53 (Mon)');
expect(formatWeek('2008-01-01')).toEqual('01 (Tue)');
expect(formatWeek('2008-12-31')).toEqual('53 (Wed)');
expect(formatWeek('2014-01-01')).toEqual('01 (Wed)');
expect(formatWeek('2014-12-31')).toEqual('53 (Wed)');
expect(formatWeek('2009-01-01')).toEqual('01 (Thu)');
expect(formatWeek('2009-12-31')).toEqual('53 (Thu)');
expect(formatWeek('2010-01-01')).toEqual('00 (Fri)');
expect(formatWeek('2010-12-31')).toEqual('52 (Fri)');
expect(formatWeek('2011-01-01')).toEqual('00 (Sat)');
expect(formatWeek('2011-01-02')).toEqual('01 (Sun)');
expect(formatWeek('2011-01-03')).toEqual('01 (Mon)');
expect(formatWeek('2011-12-31')).toEqual('52 (Sat)');
expect(formatWeek('2012-01-01')).toEqual('01 (Sun)');
expect(formatWeek('2012-01-02')).toEqual('01 (Mon)');
expect(formatWeek('2012-12-31')).toEqual('53 (Mon)');
});
it('should treat single quoted strings as string literals', function() {
expect(date(midnight, "yyyy'de' 'a'x'dd' 'adZ' h=H:m:saZ")).
toEqual('2010de axdd adZ 12=0:5:8AM-0500');
+74
View File
@@ -104,6 +104,43 @@ describe('Filter: orderBy', function() {
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
}).toThrow();
});
it('should not reverse array of objects with no predicate', function() {
var array = [
{ id: 2 },
{ id: 1 },
{ id: 4 },
{ id: 3 }
];
expect(orderBy(array)).toEqualData(array);
});
it('should not reverse array of objects with null prototype and no predicate', function() {
var array = [2,1,4,3].map(function(id) {
var obj = Object.create(null);
obj.id = id;
return obj;
});
expect(orderBy(array)).toEqualData(array);
});
it('should sort nulls as Array.prototype.sort', function() {
var array = [
{ id: 2 },
null,
{ id: 3 },
null
];
expect(orderBy(array)).toEqualData([
{ id: 2 },
{ id: 3 },
null,
null
]);
});
});
@@ -210,5 +247,42 @@ describe('Filter: orderBy', function() {
return orderBy([{"Tip %": .15}, {"Tip %": .25}, {"Tip %": .40}], '"Tip %\'');
}).toThrow();
});
it('should not reverse array of objects with no predicate', function() {
var array = [
{ id: 2 },
{ id: 1 },
{ id: 4 },
{ id: 3 }
];
expect(orderBy(array)).toEqualData(array);
});
it('should not reverse array of objects with null prototype and no predicate', function() {
var array = [2,1,4,3].map(function(id) {
var obj = Object.create(null);
obj.id = id;
return obj;
});
expect(orderBy(array)).toEqualData(array);
});
it('should sort nulls as Array.prototype.sort', function() {
var array = [
{ id: 2 },
null,
{ id: 3 },
null
];
expect(orderBy(array)).toEqualData([
{ id: 2 },
{ id: 3 },
null,
null
]);
});
});
});
+9 -38
View File
@@ -4,36 +4,7 @@
describe('$httpBackend', function() {
var $backend, $browser, callbacks,
xhr, fakeDocument, callback,
fakeTimeoutId = 0;
// TODO(vojta): should be replaced by $defer mock
function fakeTimeout(fn, delay) {
fakeTimeout.fns.push(fn);
fakeTimeout.delays.push(delay);
fakeTimeout.ids.push(++fakeTimeoutId);
return fakeTimeoutId;
}
fakeTimeout.fns = [];
fakeTimeout.delays = [];
fakeTimeout.ids = [];
fakeTimeout.flush = function() {
var len = fakeTimeout.fns.length;
fakeTimeout.delays = [];
fakeTimeout.ids = [];
while (len--) fakeTimeout.fns.shift()();
};
fakeTimeout.cancel = function(id) {
var i = fakeTimeout.ids.indexOf(id);
if (i >= 0) {
fakeTimeout.fns.splice(i, 1);
fakeTimeout.delays.splice(i, 1);
fakeTimeout.ids.splice(i, 1);
return true;
}
return false;
};
xhr, fakeDocument, callback;
beforeEach(inject(function($injector) {
@@ -57,7 +28,7 @@ describe('$httpBackend', function() {
})
}
};
$backend = createHttpBackend($browser, createMockXhr, fakeTimeout, callbacks, fakeDocument);
$backend = createHttpBackend($browser, createMockXhr, $browser.defer, callbacks, fakeDocument);
callback = jasmine.createSpy('done');
}));
@@ -154,7 +125,7 @@ describe('$httpBackend', function() {
xhr = MockXhr.$$lastInstance;
spyOn(xhr, 'abort');
fakeTimeout.flush();
$browser.defer.flush();
expect(xhr.abort).toHaveBeenCalledOnce();
xhr.status = 0;
@@ -171,9 +142,9 @@ describe('$httpBackend', function() {
xhr = MockXhr.$$lastInstance;
spyOn(xhr, 'abort');
expect(fakeTimeout.delays[0]).toBe(2000);
expect($browser.deferredFns[0].time).toBe(2000);
fakeTimeout.flush();
$browser.defer.flush();
expect(xhr.abort).toHaveBeenCalledOnce();
xhr.status = 0;
@@ -227,13 +198,13 @@ describe('$httpBackend', function() {
xhr = MockXhr.$$lastInstance;
spyOn(xhr, 'abort');
expect(fakeTimeout.delays[0]).toBe(2000);
expect($browser.deferredFns[0].time).toBe(2000);
xhr.status = 200;
xhr.onload();
expect(callback).toHaveBeenCalledOnce();
expect(fakeTimeout.delays.length).toBe(0);
expect($browser.deferredFns.length).toBe(0);
expect(xhr.abort).not.toHaveBeenCalled();
});
@@ -342,12 +313,12 @@ describe('$httpBackend', function() {
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback, null, 2000);
expect(fakeDocument.$$scripts.length).toBe(1);
expect(fakeTimeout.delays[0]).toBe(2000);
expect($browser.deferredFns[0].time).toBe(2000);
var script = fakeDocument.$$scripts.shift(),
callbackId = script.src.match(SCRIPT_URL)[2];
fakeTimeout.flush();
$browser.defer.flush();
expect(fakeDocument.$$scripts.length).toBe(0);
expect(callback).toHaveBeenCalledOnce();
+108
View File
@@ -991,6 +991,15 @@ describe('$http', function() {
$http({ method: 'POST', url: '/url', data: blob });
});
it('should ignore FormData objects', function() {
if (!window.FormData) return;
var formData = new FormData();
formData.append('angular', 'is great');
$httpBackend.expect('POST', '/url', '[object FormData]').respond('');
$http({ method: 'POST', url: '/url', data: formData });
});
it('should have access to request headers', function() {
$httpBackend.expect('POST', '/url', 'header1').respond(200);
@@ -1046,6 +1055,16 @@ describe('$http', function() {
});
it('should ignore leading/trailing whitespace', function() {
$httpBackend.expect('GET', '/url').respond(' \n {"foo":"bar","baz":23} \r\n \n ');
$http({method: 'GET', url: '/url'}).success(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[0]).toEqual({foo: 'bar', baz: 23});
});
it('should deserialize json numbers when response header contains application/json',
function() {
$httpBackend.expect('GET', '/url').respond('123', {'Content-Type': 'application/json'});
@@ -1132,6 +1151,16 @@ describe('$http', function() {
});
it('should retain security prefix if response is not json', function() {
$httpBackend.expect('GET', '/url').respond(')]}\',\n This is not JSON !');
$http({method: 'GET', url: '/url'}).success(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[0]).toEqual(')]}\',\n This is not JSON !');
});
it('should not attempt to deserialize json when HEAD request', function() {
//per http spec for Content-Type, HEAD request should return a Content-Type header
//set to what the content type would have been if a get was sent
@@ -1173,6 +1202,20 @@ describe('$http', function() {
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[0]).toEqual('{{some}}');
});
it('should not deserialize json when the opening and closing brackets do not match',
function() {
$httpBackend.expect('GET', '/url1').respond('[Code](url): function() {}');
$httpBackend.expect('GET', '/url2').respond('{"is": "not"} ["json"]');
$http.get('/url1').success(callback);
$http.get('/url2').success(callback);
$httpBackend.flush();
expect(callback.calls.length).toBe(2);
expect(callback.calls[0].args[0]).toEqual('[Code](url): function() {}');
expect(callback.calls[1].args[0]).toEqual('{"is": "not"} ["json"]');
}
);
});
@@ -1189,6 +1232,19 @@ describe('$http', function() {
expect(callback.mostRecentCall.args[0]).toBe('header1');
});
it('should have access to response status', function() {
$httpBackend.expect('GET', '/url').respond(200, 'response', {h1: 'header1'});
$http.get('/url', {
transformResponse: function(data, headers, status) {
return status;
}
}).success(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
expect(callback.mostRecentCall.args[0]).toBe(200);
});
it('should pipeline more functions', function() {
function first(d, h) {return d + '-first' + ':' + h('h1');}
@@ -1352,6 +1408,24 @@ describe('$http', function() {
}));
it('should not share the pending cached headers object instance', inject(function($rootScope) {
var firstResult;
callback.andCallFake(function(result) {
expect(result.headers()).toEqual(firstResult.headers());
expect(result.headers()).not.toBe(firstResult.headers());
});
$httpBackend.expect('GET', '/url').respond(200, 'content', {'content-encoding': 'gzip', 'server': 'Apache'});
$http({method: 'GET', url: '/url', cache: cache}).then(function(result) {
firstResult = result;
});
$http({method: 'GET', url: '/url', cache: cache}).then(callback);
$httpBackend.flush();
expect(callback).toHaveBeenCalledOnce();
}));
it('should cache status code as well', inject(function($rootScope) {
doFirstCacheRequest('GET', 201);
callback.andCallFake(function(r, status, h) {
@@ -1381,6 +1455,40 @@ describe('$http', function() {
});
it('should preserve config object when resolving from cache', function() {
$httpBackend.expect('GET', '/url').respond(200, 'content');
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'bar'}});
$httpBackend.flush();
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'baz'}}).then(callback);
$rootScope.$digest();
expect(callback.mostRecentCall.args[0].config.headers.foo).toBe('baz');
});
it('should preserve config object when resolving from pending cache', function() {
$httpBackend.expect('GET', '/url').respond(200, 'content');
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'bar'}});
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'baz'}}).then(callback);
$httpBackend.flush();
expect(callback.mostRecentCall.args[0].config.headers.foo).toBe('baz');
});
it('should preserve config object when rejecting from pending cache', function() {
$httpBackend.expect('GET', '/url').respond(404, 'content');
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'bar'}});
$http({method: 'GET', url: '/url', cache: cache, headers: {foo: 'baz'}}).catch(callback);
$httpBackend.flush();
expect(callback.mostRecentCall.args[0].config.headers.foo).toBe('baz');
});
it('should allow the cached value to be an empty string', function() {
cache.put('/abc', '');
+68
View File
@@ -58,6 +58,74 @@ describe('$interpolate', function() {
expect($interpolate("Hello, world!{{bloop}}")()).toBe("Hello, world!");
}));
describe('watching', function() {
it('should be watchable with any input types', inject(function($interpolate, $rootScope) {
var lastVal;
$rootScope.$watch($interpolate('{{i}}'), function(val) {
lastVal = val;
});
$rootScope.$apply();
expect(lastVal).toBe('');
$rootScope.i = null;
$rootScope.$apply();
expect(lastVal).toBe('');
$rootScope.i = '';
$rootScope.$apply();
expect(lastVal).toBe('');
$rootScope.i = 0;
$rootScope.$apply();
expect(lastVal).toBe('0');
$rootScope.i = [0];
$rootScope.$apply();
expect(lastVal).toBe('[0]');
$rootScope.i = {a: 1, b: 2};
$rootScope.$apply();
expect(lastVal).toBe('{"a":1,"b":2}');
}));
it('should be watchable with literal values', inject(function($interpolate, $rootScope) {
var lastVal;
$rootScope.$watch($interpolate('{{1}}{{"2"}}{{true}}{{[false]}}{{ {a: 2} }}'), function(val) {
lastVal = val;
});
$rootScope.$apply();
expect(lastVal).toBe('12true[false]{"a":2}');
expect($rootScope.$countWatchers()).toBe(0);
}));
it('should respect one-time bindings for each individual expression', inject(function($interpolate, $rootScope) {
var calls = [];
$rootScope.$watch($interpolate('{{::a | limitTo:1}} {{::s}} {{::i | number}}'), function(val) {
calls.push(val);
});
$rootScope.$apply();
expect(calls.length).toBe(1);
$rootScope.a = [1];
$rootScope.$apply();
expect(calls.length).toBe(2);
expect(calls[1]).toBe('[1] ');
$rootScope.a = [0];
$rootScope.$apply();
expect(calls.length).toBe(2);
$rootScope.i = $rootScope.a = 123;
$rootScope.s = 'str!';
$rootScope.$apply();
expect(calls.length).toBe(3);
expect(calls[2]).toBe('[1] str! 123');
expect($rootScope.$countWatchers()).toBe(0);
}));
});
describe('interpolation escaping', function() {
var obj;
+32 -6
View File
@@ -554,10 +554,24 @@ describe('$location', function() {
});
it('should throw error when invalid hashbang prefix given', function() {
expect(function() {
url.$$parse('http://www.server.org:1234/base#/path');
}).toThrowMinErr('$location', 'ihshprfx', 'Invalid url "http://www.server.org:1234/base#/path", missing hash prefix "#!".');
it('should insert default hashbang if a hash is given with no hashbang prefix', function() {
url.$$parse('http://www.server.org:1234/base#/path');
expect(url.absUrl()).toBe('http://www.server.org:1234/base#!#%2Fpath');
expect(url.hash()).toBe('/path');
expect(url.path()).toBe('');
url.$$parse('http://www.server.org:1234/base#');
expect(url.absUrl()).toBe('http://www.server.org:1234/base');
expect(url.hash()).toBe('');
expect(url.path()).toBe('');
});
it('should ignore extra path segments if no hashbang is given', function() {
url.$$parse('http://www.server.org:1234/base/extra/path');
expect(url.absUrl()).toBe('http://www.server.org:1234/base');
expect(url.path()).toBe('');
expect(url.hash()).toBe('');
});
@@ -647,6 +661,18 @@ describe('$location', function() {
};
}
describe('location watch', function() {
beforeEach(initService({supportHistory: true}));
beforeEach(inject(initBrowser({url:'http://new.com/a/b#'})));
it('should not update browser if only the empty hash fragment is cleared by updating the search', inject(function($rootScope, $browser, $location) {
$browser.url('http://new.com/a/b#');
var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').andCallThrough();
$rootScope.$digest();
expect($browserUrl).not.toHaveBeenCalled();
}));
});
describe('wiring', function() {
beforeEach(initService({html5Mode:false,hashPrefix: '!',supportHistory: true}));
@@ -1232,7 +1258,7 @@ describe('$location', function() {
});
it('should not rewrite full url links do different domain', function() {
it('should not rewrite full url links to different domain', function() {
configureService({linkHref: 'http://www.dot.abc/a?b=c', html5Mode: true});
inject(
initBrowser(),
@@ -1298,7 +1324,7 @@ describe('$location', function() {
it ('should not rewrite links when rewriting links is disabled', function() {
configureService('/a?b=c', true, true, '', 'some content', false);
configureService({linkHref: 'link?a#b', html5Mode: {enabled: true, rewriteLinks:false}, supportHist: true});
inject(
initBrowser(),
initLocation(),
+29 -3
View File
@@ -276,6 +276,10 @@ describe('parser', function() {
expect(scope.$eval("2>=1")).toEqual(2 >= 1);
expect(scope.$eval("true==2<3")).toEqual(true == 2 < 3);
expect(scope.$eval("true===2<3")).toEqual(true === 2 < 3);
expect(scope.$eval("true===3===3")).toEqual(true === 3 === 3);
expect(scope.$eval("3===3===true")).toEqual(3 === 3 === true);
expect(scope.$eval("3 >= 3 > 2")).toEqual(3 >= 3 > 2);
});
it('should parse logical', function() {
@@ -508,11 +512,18 @@ describe('parser', function() {
});
it('should evaluate function call from a return value', function() {
scope.val = 33;
scope.getter = function() { return function() { return this.val; }; };
scope.getter = function() { return function() { return 33; }; };
expect(scope.$eval("getter()()")).toBe(33);
});
// There is no "strict mode" in IE9
if (!msie || msie > 9) {
it('should set no context to functions returned by other functions', function() {
scope.getter = function() { return function() { expect(this).toBeUndefined(); }; };
scope.$eval("getter()()");
});
}
it('should evaluate multiplication and division', function() {
scope.taxRate = 8;
scope.subTotal = 100;
@@ -696,6 +707,7 @@ describe('parser', function() {
throw "IT SHOULD NOT HAVE RUN";
};
expect(scope.$eval('false && run()')).toBe(false);
expect(scope.$eval('false && true && run()')).toBe(false);
});
it('should short-circuit OR operator', function() {
@@ -703,6 +715,7 @@ describe('parser', function() {
throw "IT SHOULD NOT HAVE RUN";
};
expect(scope.$eval('true || run()')).toBe(true);
expect(scope.$eval('true || false || run()')).toBe(true);
});
@@ -758,7 +771,7 @@ describe('parser', function() {
scope.$eval('{}.toString.constructor.a = 1');
}).toThrowMinErr(
'$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
'Expression: {}.toString.constructor.a = 1');
'Expression: toString.constructor.a');
expect(function() {
scope.$eval('{}.toString["constructor"]["constructor"] = 1');
@@ -1822,6 +1835,19 @@ describe('parser', function() {
$rootScope.fn = function() {};
expect($rootScope.$eval('foo + "bar" + fn()')).toBe('bar');
}));
it('should treat properties named null/undefined as normal properties', inject(function($rootScope) {
expect($rootScope.$eval("a.null.undefined.b", {a:{null:{undefined:{b: 1}}}})).toBe(1);
}));
it('should not allow overriding null/undefined keywords', inject(function($rootScope) {
expect($rootScope.$eval('null.a', {null: {a: 42}})).toBeUndefined();
}));
it('should allow accessing null/undefined properties on `this`', inject(function($rootScope) {
$rootScope.null = {a: 42};
expect($rootScope.$eval('this.null.a')).toBe(42);
}));
});
});
});
+7
View File
@@ -1243,6 +1243,13 @@ describe('Scope', function() {
expect($rootScope.log).toBe('12');
}));
it('should allow passing locals to the expression', inject(function($rootScope) {
$rootScope.log = '';
$rootScope.$evalAsync('log = log + a', {a: 1});
$rootScope.$digest();
expect($rootScope.log).toBe('1');
}));
it('should run async expressions in their proper context', inject(function($rootScope) {
var child = $rootScope.$new();
$rootScope.ctx = 'root context';
+2 -1
View File
@@ -64,9 +64,10 @@ describe('$sniffer', function() {
it('should claim that IE9 doesn\'t have support for "oninput"', function() {
// IE9 implementation is fubared, so it's better to pretend that it doesn't have the support
// IE10+ implementation is fubared when mixed with placeholders
mockDivElement = {oninput: noop};
expect($sniffer.hasEvent('input')).toBe((msie == 9) ? false : true);
expect($sniffer.hasEvent('input')).toBe(!(msie && msie <= 11));
});
});
+28 -6
View File
@@ -16,16 +16,24 @@ describe('$templateRequest', function() {
expect(content).toBe('<div>abc</div>');
}));
it('should cache the request using $templateCache to prevent extra downloads',
inject(function($rootScope, $templateRequest, $templateCache) {
it('should cache the request to prevent extra downloads',
inject(function($rootScope, $templateRequest, $httpBackend) {
$templateCache.put('tpl.html', 'matias');
$httpBackend.expectGET('tpl.html').respond('matias');
var content;
$templateRequest('tpl.html').then(function(html) { content = html; });
var content = [];
function tplRequestCb(html) {
content.push(html);
}
$templateRequest('tpl.html').then(tplRequestCb);
$httpBackend.flush();
$templateRequest('tpl.html').then(tplRequestCb);
$rootScope.$digest();
expect(content).toBe('matias');
expect(content[0]).toBe('matias');
expect(content[1]).toBe('matias');
}));
it('should throw an error when the template is not found',
@@ -43,6 +51,20 @@ describe('$templateRequest', function() {
}).toThrowMinErr('$compile', 'tpload', 'Failed to load template: tpl.html');
}));
it('should not throw when the template is not found and ignoreRequestError is true',
inject(function($rootScope, $templateRequest, $httpBackend) {
$httpBackend.expectGET('tpl.html').respond(404);
var err;
$templateRequest('tpl.html', true).catch(function(reason) { err = reason; });
$rootScope.$digest();
$httpBackend.flush();
expect(err.status).toBe(404);
}));
it('should not throw an error when the template is empty',
inject(function($rootScope, $templateRequest, $httpBackend) {
+5 -5
View File
@@ -5316,7 +5316,7 @@ describe("ngAnimate", function() {
//jQuery doesn't handle SVG elements natively. Instead, an add-on library
//is required which is called jquery.svg.js. Therefore, when jQuery is
//active here there is no point to test this since it won't work by default.
if (!$sniffer.transitions || !_jqLiteMode) return;
if (!$sniffer.transitions) return;
ss.addRule('circle.ng-enter', '-webkit-transition:1s linear all;' +
'transition:1s linear all;');
@@ -5336,13 +5336,13 @@ describe("ngAnimate", function() {
var child = element.find('circle');
expect(child.hasClass('ng-enter')).toBe(true);
expect(child.hasClass('ng-enter-active')).toBe(true);
expect(jqLiteHasClass(child[0], 'ng-enter')).toBe(true);
expect(jqLiteHasClass(child[0], 'ng-enter-active')).toBe(true);
browserTrigger(child, 'transitionend', { timeStamp: Date.now() + 1000, elapsedTime: 1 });
expect(child.hasClass('ng-enter')).toBe(false);
expect(child.hasClass('ng-enter-active')).toBe(false);
expect(jqLiteHasClass(child[0], 'ng-enter')).toBe(false);
expect(jqLiteHasClass(child[0], 'ng-enter-active')).toBe(false);
}));
+48
View File
@@ -481,6 +481,54 @@ describe('$aria', function() {
});
});
describe('accessible actions', function() {
beforeEach(injectScopeAndCompiler);
var clickFn;
it('should a trigger click from the keyboard', function() {
scope.someAction = function() {};
compileInput('<div ng-click="someAction()" tabindex="0"></div>');
clickFn = spyOn(scope, 'someAction');
element.triggerHandler({type: 'keypress', keyCode: 32});
expect(clickFn).toHaveBeenCalled();
});
it('should not override existing ng-keypress', function() {
scope.someOtherAction = function() {};
var keypressFn = spyOn(scope, 'someOtherAction');
scope.someAction = function() {};
clickFn = spyOn(scope, 'someAction');
compileInput('<div ng-click="someAction()" ng-keypress="someOtherAction()" tabindex="0"></div>');
element.triggerHandler({type: 'keypress', keyCode: 32});
expect(clickFn).not.toHaveBeenCalled();
expect(keypressFn).toHaveBeenCalled();
});
});
describe('actions when bindKeypress set to false', function() {
beforeEach(configAriaProvider({
bindKeypress: false
}));
beforeEach(injectScopeAndCompiler);
it('should not a trigger click from the keyboard', function() {
scope.someAction = function() {};
var clickFn = spyOn(scope, 'someAction');
element = $compile('<div ng-click="someAction()" tabindex="0">></div>')(scope);
element.triggerHandler({type: 'keypress', keyCode: 32});
expect(clickFn).not.toHaveBeenCalled();
});
});
describe('tabindex when disabled', function() {
beforeEach(configAriaProvider({
tabindex: false
+30 -11
View File
@@ -1059,17 +1059,6 @@ describe('ngMock', function() {
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK');
});
it('should take function', function() {
hb.expect('GET', '/some').respond(function(m, u, d, h) {
return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}, 'Moved Permanently'];
});
hb('GET', '/some', 'data', callback, {a: 'b'});
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive', 'Moved Permanently');
});
it('should default status code to 200', function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(200);
@@ -1085,6 +1074,24 @@ describe('ngMock', function() {
expect(callback.callCount).toBe(2);
});
it('should default status code to 200 and provide status text', function() {
hb.expect('GET', '/url1').respond('first', {'header': 'val'}, 'OK');
hb('GET', '/url1', null, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'first', 'header: val', 'OK');
});
it('should take function', function() {
hb.expect('GET', '/some').respond(function(m, u, d, h) {
return [301, m + u + ';' + d + ';a=' + h.a, {'Connection': 'keep-alive'}, 'Moved Permanently'];
});
hb('GET', '/some', 'data', callback, {a: 'b'});
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(301, 'GET/some;data;a=b', 'Connection: keep-alive', 'Moved Permanently');
});
it('should default response headers to ""', function() {
hb.expect('GET', '/url1').respond(200, 'first');
@@ -1303,6 +1310,18 @@ describe('ngMock', function() {
});
it('should abort requests when timeout passed as a numeric value', inject(function($timeout) {
hb.expect('GET', '/url1').respond(200);
hb('GET', '/url1', null, callback, null, 200);
$timeout.flush(300);
expect(callback).toHaveBeenCalledWith(-1, undefined, '');
hb.verifyNoOutstandingExpectation();
hb.verifyNoOutstandingRequest();
}));
it('should throw an exception if no response defined', function() {
hb.when('GET', '/test');
expect(function() {
+9 -3
View File
@@ -10,14 +10,20 @@ describe('linky', function() {
}));
it('should do basic filter', function() {
expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c")).
expect(linky("http://ab/ (http://a/) <http://a/> http://1.2/v:~-123. c “http://example.com” http://me.com")).
toEqual('<a href="http://ab/">http://ab/</a> ' +
'(<a href="http://a/">http://a/</a>) ' +
'&lt;<a href="http://a/">http://a/</a>&gt; ' +
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c');
'<a href="http://1.2/v:~-123">http://1.2/v:~-123</a>. c ' +
'&#8220;<a href="http://example.com">http://example.com</a>&#8221; ' +
'&#8216;<a href="http://me.com">http://me.com</a>&#8217;');
expect(linky(undefined)).not.toBeDefined();
});
it('should handle www.', function() {
expect(linky('www.example.com')).toEqual('<a href="http://www.example.com">www.example.com</a>');
});
it('should handle mailto:', function() {
expect(linky("mailto:me@example.com")).
toEqual('<a href="mailto:me@example.com">me@example.com</a>');
@@ -30,7 +36,7 @@ describe('linky', function() {
});
it('should handle quotes in the email', function() {
expect(linky('foo@"bar.com')).toEqual('<a href="mailto:foo@&#34;bar.com">foo@&#34;bar.com</a>');
expect(linky('foo@"bar".com')).toEqual('<a href="mailto:foo@&#34;bar&#34;.com">foo@&#34;bar&#34;.com</a>');
});
it('should handle target:', function() {