Compare commits

...

115 Commits

Author SHA1 Message Date
Igor Minar 3efdeebcb7 chore(release): cut 1.2.2 consciousness-inertia release 2013-11-22 09:05:42 -08:00
Igor Minar 16febf8357 docs(CHANGELOG): add release notes for 1.2.2 consciousness-inertia 2013-11-22 09:03:40 -08:00
Caitlin Potter 0f7c4ca671 chore(style): fix missing indentation in httpBackend from a3172a2 2013-11-22 15:03:12 +00:00
Pete Bacon Darwin a3172a285f fix($httpBackend): only IE8 and below can't use script.onload for JSONP
IE8, IE9 and IE10 can use `script.onreadystate` so up till now we have been using this
if the sniffer says we are on IE.
But IE11 now does not support `script.onreadystate` and only supports the more standard
`script.onload` and `script.onerror`.
IE9 and IE10 do support `script.onload` and `script.onerror`. So now we only test whether
we are on IE8 or earlier before using `script.onreadystate`.
See http://pieisgood.org/test/script-link-events/

jQuery just uses all these handlers at once and hopes for the best, but since IE9 and IE10
support both sets of handlers, this could cause the handlers to be run more than once.

jQuery also notes that there is a potential memory leak in IE unless we remove the handlers
from the script object once they are run.  So we are doing this too, now.

Closes #4523
Closes #4527
Closes #4922
2013-11-22 13:45:55 +00:00
Igor Minar 84c408ce63 test($compile): correct the assertion to make test pass on IE11 2013-11-22 00:52:57 -08:00
rodyhaddad 40647b179c fix($parse): allow for new lines in expr when promise unwrapping is on
Previously, when unwrapping promises was set to `true`,
an error would occur if a parsed expression had a
new line in it.
This was because when generating the `evaledFnGetter` code,
a new line in an parsed expression would create a new line
in a JS string in that code, which is illegal. That is:
```js
pw("A+
B")
```

Closes #4718
2013-11-22 00:19:08 -08:00
Chirayu Krishnappa 0421cb4200 fix($compile): secure form[action] & iframe[srcdoc]
Require bindings to form[action] to be $sce.RESOURCE_URL and bindings to
iframe[srcdoc] to be $sce.HTML

Closes #4927
Closes #4933
2013-11-21 23:15:15 -08:00
Gonzalo Ruiz de Villa 6f1050df4f fix(httpBackend): should not read response data when request is aborted
When a request is aborted, it makes no sense to read the response headers or text.
Also in IE9, trying to read data (either response headers or text) from an aborted request
throws an Error c00c023f.

Fixes #4913
Closes #4940
2013-11-21 23:09:46 -08:00
Corey Burrows 4d16472b91 fix(ngMock): fixes httpBackend expectation with body object
Fixes an issue with httpBackend expectations where a given body object
may not match the actual request body if its keys are serialized in a
different order.

Closes #4956
2013-11-21 22:57:15 -08:00
Pete Bacon Darwin 9e89a31b12 fix(loader): expose $$minErr to modules such asngResource
This is highlighted in angular-phonecat when you try to use the index-async.html
which needs to load the ngResource module asynchronously but fails when it tries
to call `angular.$$minErr` to create the $resourceMinErr object.

Closes #5050
2013-11-21 22:51:26 -08:00
Tobias Bosch e6521e7491 fix(ngView): Don't throw when the ngView element contains content with directives.
Fixes #5069
2013-11-21 22:20:11 -08:00
Tobias Bosch 0a7cbb33b0 fix(ngInclude): Don't throw when the ngInclude element contains content with directives.
Related to #5069
2013-11-21 22:20:11 -08:00
Tobias Bosch 579242346c fix(tests): Correct tests for IE11
Some tests were wrong. However, src/* did not contain problems.

Fixes #5046
2013-11-21 21:53:09 -08:00
Peter Deak c42d0a0418 fix(ngAnimate): correctly retain and restore existing styles during and after animation
Closes #4869
2013-11-21 23:37:01 -05:00
Matias Niemelä 3fbb25e25c chore($animate): remove unnecessary reflective dereferencing 2013-11-21 20:48:21 -05:00
Matias Niemelä 6760d7a315 fix($animate): ensure keyframe animations are blocked around the reflow
Keyframe animations trigger on the first CSS class and not the second.
This may cause a slight flicker during a stagger animation since the
animation has already started before the stagger delay is considered.
This fix ensures that the animation is blocked until the active animation
starts which allows for staggering animations to take over properly.

Closes #5018
2013-11-21 20:48:15 -05:00
Matias Niemelä 062fbed8fc fix($animate): ensure transition animations are unblocked before the dom operation occurs
Transitions are blocked when the base CSS class is added at the start of the animation. This
causes an issue if the followup CSS class contains animatable-styles. Now, once the animation
active state is triggered (when the animation CSS dom operation occurs) the animation itself
will always trigger an animate without a quick jump.

Closes #5014
Closes #4265
2013-11-21 20:48:07 -05:00
Matias Niemelä 76e4db6f3d fix($animate): ensure addClass/removeClass animations do not snap during reflow
Closes #4892
2013-11-21 20:47:55 -05:00
Matias Niemelä 0cd7e8f227 fix($compile): ensure CSS classes are added and removed only when necessary
When $compile interpolates a CSS class attribute expression it will
do so by comparing the CSS class value already present on the element.
This may lead to unexpected results when dealing with ngClass values being
added and removed therefore it is best that both compile and ngClass delegate
addClass/removeClass operations to the same block of code.
2013-11-21 20:47:44 -05:00
Vojta Jina ba1b47f85b test(docs): fix the failing specs
Because Grunt was not failing the build, we didn't noticed these failing specs.
2013-11-21 12:41:57 -08:00
Vojta Jina 0a3481e23a chore: use temporary version of grunt-jasmine-node
This should be reverted once https://github.com/jasmine-contrib/grunt-jasmine-node/pull/33 gets merged in the upstream repo.

It fixes the problem where Grunt does not fail the build, even though there are failures.
See https://travis-ci.org/angular/angular.js/builds/14329011#L2366
2013-11-21 12:41:48 -08:00
jody tate e33c365144 docs(guide/unit-testing): minor style and grammar changes
Closes #5057
2013-11-21 20:37:57 +00:00
gdi2290 e3ceb50b73 docs(faq): update compressed and minified file size
Closes #5058
2013-11-21 20:27:27 +00:00
scottywakefield 6b5772bbbd docs(guide/ie): add info about what IE versions are supported
Added text from https://github.com/angular/angular.js/issues/4974

Closes #5070
2013-11-21 20:25:41 +00:00
Andrew Silluron-Gonzalez 6288cf5ca4 fix(ngController): fix issue with ngInclude on the same element
This changes the priority of ngController to 500 so that it takes precedence
over ngInclude.

Closes #4431, #4521
2013-11-21 09:52:34 -08:00
Tobias Bosch f6ecf9a3c9 fix($resource): Always return a resource instance when calling class methods on resources.
Previously, calling `MyResource.save(myResourceInstance)`returned
a promise, in contrast to the docs for `$resource`. However,
calling `MyResource.save({name: 'Tobias"})`already correctly
returned a resource instance.

Fixes #4545.
Closes #5061.
2013-11-21 09:51:02 -08:00
Chia-liang Kao a4e6d962d7 feat(input): hold listener during text composition
When composing text in CJKV, intermediate buffer for unfinished text should not
be updating the bound scope variables.

Closes #4684
2013-11-21 09:46:33 -08:00
Igor Minar 7874a4d007 docs(guide/migration): fix typo ngHtmlBind -> ngBindHtml 2013-11-21 07:29:18 -08:00
Matias Niemelä 1d50663b38 fix(ngAnimate): use a fallback CSS property that doesn't break existing styles
The clip property seems to remove the box-shadow property when an absolute
positioned animation is ongoing. This fix changes the property to be border-spacing
which is also very underused. The border-spacing CSS property is only visible
when border-collapse is set to separate.

Closes #4902
Closes #5030
2013-11-20 20:54:07 -05:00
Tobias Bosch ec3c4f94c7 refactor($sce): Use $sniffer instead of $document for feature detection.
Also adds `$sniffer.msieDocumentMode` property.

Closes #4931
Closes #5045
2013-11-20 23:12:39 +00:00
Matias Niemelä 6b8bbe4d90 fix(ngClass): ensure that ngClass only adds/removes the changed classes
ngClass works by removing all the former classes and then adding all the
new classes to the element during each watch change operation. This may
cause transition animations to never render. The ngClass directive will
now only add and remove the classes that change during each watch operation.

Closes #4960
Closes #4944
2013-11-20 17:15:56 -05:00
Matias Niemelä 7067a8fb0b fix($animate): ensure the DOM operation isn't run twice
Depending on the animations placed on ngClass, the DOM operation may
run twice causing a race condition between addClass and removeClass.
Depending on what classes are removed and added via $compile this may
cause all CSS classes to be removed accidentally from the element
being animated.

Closes #4949
2013-11-20 17:08:03 -05:00
Brian Ford c47abd0dd7 fix(ngInclude): allow ngInclude to load scripts when jQuery is included
In 1.2, the behavior of ngInclude was modified to use DOM APIs rather than jqLite. This means that
even when jQuery was loaded, ngInclude was not calling into it, and thus scripts were not eval'd
as they had been before. Although the use of ngInclude to eval scripts as a lazy-loading strategy
was never an intentional feature, this patch restores the ability to do so.

Closes #3756
2013-11-20 13:58:54 -08:00
Michel Salib 68d71bbc01 docs($log): the documented default log behavior was incorrect
Closes #4953
2013-11-20 21:02:02 +00:00
Jarrett Harris 334303e485 docs(ngAnimate): fixed two small typos
Line 162: 'defiend' should be 'defined'
Line 225: 'callback function be excuted' should be 'callback function will be executed'.

Closes #5048
2013-11-20 14:32:30 +00:00
gdennie b95fd53c6e docs(booleanAtts): explain the motivation for boolean attributes
It was not explicitly and consistently stated that the transient nature of boolean
attributes precludes them from hosting binding expressions.
This change make that more clear and reinforces the simplicity and elegance of the solution.

Closes #5031
2013-11-20 14:30:37 +00:00
Ashutosh Das c77dd040b4 docs(tutorial/step-2): correct the link to jasmine docs
Closes #5029
2013-11-20 14:21:49 +00:00
Stéphane Reynaud dc027f22e5 docs(ngRepeat): fix typo
Replace "ian" in "in"

Closes #5027
2013-11-20 14:10:23 +00:00
Stéphane Reynaud 043500fb27 docs(ngPluralize): Fix missing space before parentheses
There should be a space between "braces" and "(`{}`)"

Closes #5026
2013-11-20 11:38:29 +00:00
Maksim 3ceb6ab477 docs(guide/directive): use hideDialog handler in example
The handler is in the controller but was not being used in the template.

Closes #5020
2013-11-20 11:37:00 +00:00
gipsy86147 999fa44616 docs(guide/ie): fix typo
Closes #5006
2013-11-20 11:31:56 +00:00
Dave Gaeddert 5f9121ad56 docs(guide/providers): remove extra closing parenthesis in example
Closes #5005
2013-11-20 11:30:45 +00:00
Tyler Eich b4cf8483d7 docs(guide/migration): fix typo
Closes #5002
2013-11-20 11:26:57 +00:00
Yves Richard 8a9816e06b docs(guide/compiler): fix typo in isolate scope def
Closes #4999
2013-11-20 11:25:20 +00:00
jbnizet c0e10683a6 docs(api): example for $provide.value() uses $provide.value()
The example code for `$provide.value()` actually used `$provide.constant()`.
It now uses `$provide.value()`.

Closes #4983
Closes #4990
2013-11-20 11:21:09 +00:00
Pete Bacon Darwin cad5a367c3 docs(ngRoute): make it easier to find the example
Closes #4975
2013-11-20 11:18:21 +00:00
Stéphane Reynaud f2453eabb3 docs(tutorial): minimum node.js version is 0.10 (Windows too)
The doc has been modified by the following commit: bcc6e8d4f6
But the change was not made ​​for the part of Windows.

Closes #4967
2013-11-20 11:02:30 +00:00
Jayson Harshbarger aa0b11d794 docs(guide/migration): fix typo
Closes #4965
2013-11-20 11:00:01 +00:00
Matias Niemelä b9fa5c5a67 docs($animate): update the docs explaining enable/disable for specific elements 2013-11-19 17:50:24 -05:00
Vojta Jina 751f058f30 chore(travis): increase disconnect timeout
I still see some disconnection issues with IE9, hopefully this will help a bit.
2013-11-18 16:14:09 -08:00
Pete Bacon Darwin 29274e1d8d docs(ngApp): improve description and example 2013-11-18 16:21:59 +00:00
Jens Berthold 23ba287897 docs(guide/directive): clarify code example for isolated scopes bindings
Use different names for the attribute on the element (`info`) and the property (`customerInfo`)
on the isolate scope. Before `customer` was used for both which made it harder to understand.

Closes #4825
2013-11-18 15:03:35 +00:00
Pete Bacon Darwin 8f1e3606dd docs(guide/directive): add note about HTML case-insensitivity
Closes #4719
2013-11-18 12:58:53 +00:00
Chance ac56d1c9d9 docs(tutorial/step-4): controllers are no longer global functions
The docs did not line up with the codebase / previous steps of the tutorial.

Closes #4988
2013-11-18 12:49:19 +00:00
Pete Bacon Darwin de2919cb9a docs(guide/i18n): fix link to i18n files in the project
Closes #4998
2013-11-18 11:55:30 +00:00
Christoph Burgdorf 61943276f0 chore(*): remove accidentally created file
Closes #4963
2013-11-15 21:46:13 +00:00
Vojta Jina 88ce00a3cf chore(release): start a new release iteration 2013-11-15 00:03:03 -08:00
Vojta Jina a3eb6baf58 chore(release): cut the 1.2.1 underscore-empathy release 2013-11-14 22:33:20 -08:00
Vojta Jina f4fcaa8757 docs: fix the "show source" button
Closes #4904
2013-11-14 21:38:53 -08:00
Jeff Cross 40e34a924b docs(ngResource): removed buzz client example
The Buzz Client example on the ngResource
doc was causing parse errors.

While the root cause is being investigated,
the example has been removed, and should be
replaced by a more relevant example anyhow.
2013-11-14 21:22:11 -08:00
Tobias Bosch 90f87072e8 fix($compile): accessing controllers of transcluded directives from children
Additional API (backwards compatible)
- Injects `$transclude` (see directive controllers) as 5th argument to directive link functions.
- `$transclude` takes an optional scope as first parameter that overrides the
  bound scope.

Deprecations:
- `transclude` parameter of directive compile functions (use the new parameter for link functions instead).

Refactorings:
- Don't use comment node to temporarily store controllers
- `ngIf`, `ngRepeat`, ... now all use `$transclude`

Closes #4935.
2013-11-14 20:59:50 -08:00
Tobias Bosch c785918cbd refactor($compile): move function def out of loop 2013-11-14 20:53:30 -08:00
Vojta Jina 8425e9fe38 fix(loader): don't rely on internal APIs
This significantly increases the size of the loader:
- minified: 1031bytes -> 1509bytes (+46%)
- minified + gzip: 593bytes -> 810bytes (+36%)

I'm not entirely sold on the idea of shipping minErr with the loade. With the current state, the angular-loader behavior is completely broken - this is just a quick fix, we can revisit this change in the future.


Closes #4437
Closes #4874
2013-11-14 15:05:39 -08:00
Vojta Jina 94764ee089 fix(minErr): remove references to internals APIs
So that we can use minErr with angular-loader, before full angular is loaded.

This also fixes replacing the version during the build.
2013-11-14 15:00:53 -08:00
James deBoer 04492ef227 chore(mocks): Remove reference to flushNext
Closes #4885
2013-11-14 16:07:36 +00:00
victorbjelkholm c6016a6a85 docs(tutorial): change controllers to not have name twice
While giving the controller function a name helps with debugging,
since otherwise your controller will be anonymous in stack traces,
passing the name to both the `controller()` method and as the function name
is confusing for beginners.

Closes #4415
2013-11-14 14:47:01 +00:00
Pete Bacon Darwin 977e2f55de docs(ngClass): fix e2e test for example
Broken by fd7bca22e1
2013-11-14 14:20:31 +00:00
Caitlin Potter 4184ff8ff7 docs(NgModelController): document $viewChangeListeners property
Closes #4948
2013-11-14 14:13:22 +00:00
Stéphane Reynaud f3d4fe6209 docs(FormController): remove unnecessary parenthesis
Closes #4936
2013-11-14 14:09:09 +00:00
andre 9e5cd92fa9 docs(misc/faq): fix invalid escaping of character
Backslash is acting as escape character so text is not properly formatted.

Closes #4923
2013-11-14 14:06:01 +00:00
smarigowda c07f1e1c9f docs(guide/concepts): controllers can also access scope
Closes #4918
2013-11-14 14:04:09 +00:00
Pete Bacon Darwin fd7bca22e1 docs(ngClass): distinguish between CSS classes and scope properties
Closes #4914
2013-11-14 14:00:06 +00:00
Mathis Hofer 8f283fe473 docs(NgModelController): clarify documentation of $setViewValue
$setViewValue does not really "Read a value from view".
It should be called to trigger the ngModel to be updated when the value in the view changes.

Closes #4907
2013-11-14 13:48:22 +00:00
Phillip Alexander cb8061c75c docs(guide/concepts): improve sentence wording
Before:

> Let's add some more logic to the example to
allow to enter and calculate the costs in different currencies and also pay the invoice.

After:

> Let's add some more logic to the example that
allows us to enter and calculate the costs in different currencies and also pay the invoice.

Closes #4903
2013-11-14 13:36:40 +00:00
rsnapp b1366c32d4 docs($q): add missing closing parentheses in code example
Closes #4900
2013-11-14 13:27:11 +00:00
Mauro Carrero b122194425 docs(tutorial/step-2): remove repeated "the"
Closes #4854
2013-11-14 13:24:26 +00:00
Pete Bacon Darwin 089bf5f0e3 docs(guide/directive): split long lines 2013-11-14 13:22:39 +00:00
xdhmoore f6fa7c9c95 docs(guide/directive): clarify sentence meaning
Closes #4873
2013-11-14 13:16:47 +00:00
mkolodny 938b2e1217 docs(resource): fix grammatical issue
'Case' should be the plural 'cases' since it is talking about multiple possible cases rather
than a single case. For slightly more info, see the section 'When words like "none" are the
subject' in this article: http://writing.wisc.edu/Handbook/SubjectVerb.html
2013-11-14 13:12:36 +00:00
Peter Kosa dbc6696b68 docs(tutorial/step-12): fix incorrect code filename
Closes #4860
2013-11-14 13:10:29 +00:00
gdi2290 5d632af926 docs(misc/contribute): fix internal links
Closes #4848
2013-11-14 13:07:32 +00:00
PatrickJS ed9e570a12 docs(downloading): fix invalid filenames and add missing modules
Closes #4846
2013-11-14 13:03:03 +00:00
Pete Bacon Darwin d7ed885984 docs(ngBindHtml): fix dependency for Plunker and JSFiddle in example
The `<doc:example>` directive does not load up the dependencies correctly.
Using the `<example>` directive, with `<file>` elements fixes this.

Closes #4951
2013-11-14 12:28:53 +00:00
Vojta Jina 4ab16aaaf7 feat($parse): revert hiding "private" properties
Hiding `_*` properties was a feature primarily for developers using Closure compiler and Google JS
style. We didn't realize how many people will be affected by this change.

We might introduce this feature in the future, probably under a config option, but it needs more
research and so I'm reverting the change for now.

This reverts commit 3d6a89e888.

Closes #4926
Closes #4842
Closes #4865
Closes #4859
Closes #4849

Conflicts:
	src/ng/parse.js
2013-11-13 23:25:09 -08:00
Jeff Cross 89f435de84 fix(urlUtils): made removal of windows drive from path safer
Prior to this fix, the urlResolve method would automatically
strip the first segment of a path if the segment ends in a colon.
This was to correct undesired behavior in the $location service
using the file protocol on windows in multiple browsers (see #4680).

However, there could be cases where users intentionally 
have first path segments that end in a colon 
(although this conflicts with section 3.3 of rfc3986).

The solution to this problem is an extra check to make sure
the first path segment of the input url does not end with a colon,
to make sure we're only removing undesired path segments.

Fixes #4939
2013-11-13 15:53:20 -08:00
Pete Bacon Darwin bcc6e8d4f6 docs(tutorial): minimum required node.js version is 0.10 2013-11-13 22:43:34 +00:00
Martin Field b2137c9fdf docs($compile): Explain that post-link functions run in reverse order.
Update the $compile docs to mention the change introduced in #4266.

Closes #4843
2013-11-13 21:58:04 +00:00
andre bee56a82b0 docs(guide/scope): correct scopes example
Remove reference to `employee` property as it's not used in the example.
Inject and use `$rootScope` applying `department` property as mentioned in text.

Closes #4839
2013-11-13 21:48:30 +00:00
Pete Bacon Darwin bcdbfdfeae docs(guide/scope): ensure demo CSS styles work in JSFiddle and Plunker
The CSS styling in the ng-scope demo was using CSS classes (`.doc-example-live` and
`.show-scope') to prevent the styling for the demo from affecting the entire page.
Unfortunately elements containing these classes did not appear in JSFiddle or Plunker
when you click edit.

This fix moves the `.show-scope' class inside the demo (renaming it `.show-scope-demo`)
and removes the reliance on `.doc-example-live` altogether.

Closes #4838
2013-11-13 21:45:07 +00:00
Tatham Oddie afbed10feb docs(contribute): justify note about elevation on Windows
This message needs a justification. Without one, it's like asking somebody
on *nix to run everything under sudo 'just because'.

Closes #4832
2013-11-13 21:24:08 +00:00
Ben Wiklund f69ee170ed docs($httpBackendSpec): fix typo in spy name
Closes #4830
2013-11-13 21:21:50 +00:00
Eddie Monge Jr a59976be18 docs(errors/compile/tplrt): display html block as code
HTML elements were getting parsed by as HTML elements

Closes #4827
2013-11-13 21:06:37 +00:00
Derek Peterson 40d1e10520 docs(guide): fix typo
Closes #4821
2013-11-13 20:55:25 +00:00
Aaditya Talwai 5bf81bc111 docs(guide/understanding_controller): fix incorrect property reference
Correct " model property `spice` " to " model property `customSpice` " to match the code sample

Closes #4812
2013-11-13 20:51:29 +00:00
Pete Bacon Darwin 96ad0c7594 docs(ngdoc): clarify extra module installation options
Closes #4811
2013-11-13 20:43:32 +00:00
Julien Sanchez 717a6705e2 docs($http): improve $http's caching documentation
Make the possibility to pass a custom cache instance to $http more obvious.

Closes #4803
2013-11-13 20:20:59 +00:00
Ari 37ac4724ba docs(guide): add ng-newsletter to weekly updates link
Closes #4793
2013-11-13 19:52:36 +00:00
Ari 8c18ef67cf docs(guide): add ng-book link to books
Closes #4792
2013-11-13 19:50:25 +00:00
Pete Bacon Darwin dfe6400537 docs(ngdoc): fix version picker grouping
The grouping of the different versions was not correct for the new 1.2.0+ releases.
Now versions are marked as stable only if they have an even number it the minor version
position (e.g. 1.0.8, 1.2.1, 1.2.0-abcde) and they are not an RC version, (e.g. 1.0.0rc3,
1.2.0-rc2).

Closes #4908
2013-11-12 23:28:27 -08:00
ROUL f925e8caa6 fix(urlUtils): urlUtils doesn't return right path for file:// on win
Chrome and other browsers on Windows often
append the drive name to the pathname,
as described in #4680. This would cause
the location service to browse to odd
URLs, such as /C:/myfile.html,
when opening apps using file://.

Fixes  #4680
2013-11-12 22:41:06 -08:00
Tobias Bosch e1254b266d fix($compile): correctly handle interpolated style in replace templates
A directive with a template with `replace: true` and an interpolated style at the root element should work correctly.

Closes #4882.
2013-11-12 16:51:16 -08:00
Andrei Korzhevskii fa82a31fa6 fix(grunt): Fix NG_VERSION_MINOR typo 2013-11-12 13:05:27 -08:00
Tobias Bosch 4612705ec2 fix(ngIf): don't create multiple elements when changing from a truthy to another thruthy value.
Fixes #4852.
2013-11-11 17:05:43 -08:00
Chirayu Krishnappa 9577702e8d fix($resource): don't use $parse for @dotted.member
params and paramDefaults support looking up the parameter value from the
data object.  The syntax for that is `@nested.property.name`.
Currently, $resource uses $parse to do this.  This is too liberal
(you can use values like `@a=b` or `@a | filter` and have it work -
which doesn't really make sense).  It also puts up a dependency on
$parse which is has restrictions to secure expressions used in
templates.  The value here, though a string, is specified in Javascript
code and shouldn't have those restrictions.
2013-11-11 16:17:34 -08:00
Peter Bacon Darwin a61b65d01b fix(angular-bootstrap): make IE8 happy 2013-11-11 00:09:15 +00:00
Miško Hevery fb483d56a7 docs($sce): ng-bind-html takes an expression {{}} 2013-11-10 23:27:13 +00:00
Pete Bacon Darwin c5c75386e4 docs(guide/migration): fix internal link 2013-11-10 00:09:25 +00:00
Caitlin Potter 2734b9f560 chore(package.json): revert to marked@0.2.9 to fix CI builds
The marked npm library brought in a breaking change at 0.2.10.
This commit ensures that we get exactly the version we want.

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