Compare commits

...

100 Commits

Author SHA1 Message Date
Brian Ford c086f831fb docs(changelog): release notes for 1.2.13 2014-02-14 16:41:02 -08:00
Igor Minar fab2d3c92f style(animate): remove ws 2014-02-14 16:31:57 -08:00
Matias Niemelä 4f84f6b3e4 fix($animate): ensure $animate doesn't break natural CSS transitions
BREAKING CHANGE: ngClass and {{ class }} will now call the `setClass`
animation callback instead of addClass / removeClass when both a
addClass/removeClass operation is being executed on the element during the animation.

Please include the setClass animation callback as well as addClass and removeClass within
your JS animations to work with ngClass and {{ class }} directives.

Closes #6019
2014-02-14 16:30:48 -08:00
Matias Niemelä cf5e463abd pref($animate): only trigger DOM callbacks if registered on the element being animated
BREAKING CHANGE: Both the `$animate:before` and `$animate:after` DOM events must be now
registered prior to the $animate operation taking place. The `$animate:close` event
can be registered anytime afterwards.

DOM callbacks used to fired for each and every animation operation that occurs within the
$animate service provided in the ngAnimate module. This may end up slowing down an
application if 100s of elements are being inserted into the page. Therefore after this
change callbacks are only fired if registered on the element being animated.
2014-02-14 16:28:56 -08:00
Matias Niemelä f288b8f010 pref($animate): group all asynchronous requests into one shared buffer 2014-02-14 16:28:34 -08:00
Matias Niemelä b7e4e92014 chore(jqLite): expose the _data lookup function to angular.element 2014-02-14 15:49:08 -08:00
Caitlin Potter 31c450bcee fix($compile) support templates with table content root nodes
If the first element in a template is a <tr>, <th>, <td>, or <tbody> tag,
the HTML compiler will ensure that the template is wrapped in a <table>
element so that the table content is not discarded.

Closes #2848
Closes #1459
Closes #3647
Closes #3241
2014-02-14 14:42:55 -05:00
Tim Whitbeck a9fcb0d0fc fix(input): don't apply textInput to <input type="file">
textInput shouldn't be applied to file inputs to ease writing of custom file input directives.

This change prevents file inputs from instantiating the text input parser/formatter pipelines.

Closes #6247
Closes #6231
2014-02-13 16:43:18 -05:00
Naomi Black a3846ab837 Merge pull request #6245 from brianhall/master
Google logo in AngularJS.exports to vector format.
2014-02-13 10:35:05 -08:00
Brian Hall 1953b0bed9 docs(logo): change logo to vector format in .eps file
Browser: Other
Component: docs
Regression: no

Closes issue #6092
2014-02-13 09:51:11 -08:00
Caitlin Potter 2b73027136 fix(input): setViewValue on compositionend
Because of a4e6d962, model is not updated on input/change between the
compositionstart and compositionend events. Unfortunately, the compositionend
event does not always happen prior to an input/change event.

This changeset calls the listener function to update the model after a
compositionend event is received.

Closes #6058
Closes #5433
2014-02-12 20:28:13 -05:00
Caitlin Potter 1079105443 docs($interpolate): fix link to $interpolateProvider#endSymbol
The markup here was missing the methods_ prefix and behaved incorrectly.

Closes #5802
2014-02-12 14:41:37 -05:00
Caitlin Potter d119e36302 docs($location): fix link to $rootScope.Scope.$on
Previously missing the methods_ prefix.

Closes #5798
2014-02-12 14:39:41 -05:00
James Kyle 98b2f8ef18 docs(currencyFilter): added missing line break in currency doc ptor test
Closes #6229
2014-02-12 10:05:35 -05:00
Stéphane Reynaud e7ab857ddb docs(guide/$location): correct link to HTML5 draft section 5.5 (history api)
Previous link url is no longer served, responds with bad link (error 404). This change corrects the
URL to point to section 5.5 of the draft. The old URL appears to have been removed from service in
2012.

Corrects the link to "History API"

Closes #6225
2014-02-12 08:26:21 -05:00
Mathieu Tricoire 46cba2e05d docs(input): document NgModelController.$isEmpty parameters / return value
Closes #6224
2014-02-12 08:19:24 -05:00
Jason Schapiro 72894f0dd2 docs(tutorial): inject phonecapApp module into unit test
When I was reading this doc I was thinking "but what about phonecatApp?" and when I looked in the
file from the step-11 branch there it is. Should be reflected in the docs as well

Closes #6209
2014-02-11 21:29:47 -05:00
Caitlin Potter 760f49de10 chore(dependencies): upgrade kriskowal/q to version ~1.0.0
CI builds on travis occasionally freak out because of the recursive use of process.nextTick, which
has been deprecated in Node relatively recently, to be replaced with setImmediate. Unfortunately,
this change does not resolve the issue. However, it does not hurt, either.

Closes #6161
2014-02-11 18:54:38 -05:00
Jesse Palmer 686b13bf60 docs(core): add closing tag to ngApp directive example
added missing closing tag to ngApp example.

Closes #6066
2014-02-11 18:42:16 -05:00
Evgeniy Tkachenko 56cc7bcc98 docs(jqLite): link to jQuery.fn.bind/unbind docs rather than jQuery.fn.on/off docs
Сorrect link.

Closes #6171
2014-02-11 18:23:25 -05:00
Caitlin Potter b4eed8ad94 feat(filterFilter): support deeply nested predicate objects
Due to 339a165, it became impossible to filter nested properties of an object using the filterFilter.
A proposed solution to this was to enable the use of nested predicate objects. This change enables the
use of these nested predicate objects.

Example:

```html
<div ng-repeat="it in items | filter:{ address: { country: 'Canuckistan'}}"></div>
```

Or

```js
$filter('filter')(items, { address: { country: 'Canuckistan' } });
```

Closes #6215
Related to #6009
2014-02-11 17:08:41 -05:00
Daniel Tabuenca 08793a690a refactor(ngTransclude): use transclusion function passed in to link
Since we now pass in the transclusion function directly to the link function, we no longer need
the old scheme whereby we saved the transclude function injected into the controller for later
use in during linking.

Additionally, this change may aid in correcting a memory leak of detached DOM nodes (see #6181
for details).

This commit removes the controller and simplifies ngTransclude.

Closes #5375
Closes #6181
2014-02-11 14:57:56 -05:00
Stéphane Reynaud ef4bf8c77c docs(guide/index): replace "shold" to "should"
Replace "shold" to "should"

Closes #6216
2014-02-11 11:46:36 -05:00
Igor Minar b6ab826c4b style(guide): remove ws 2014-02-10 17:09:35 -08:00
Jeremy Likness 71f974b459 docs(guide): add new resource links
Added a link to 10 reasons to use and online courses for Angular

Closes #6194
2014-02-10 17:09:34 -08:00
James Wagoner a68624444a docs(ngSubmit): ngSubmit also works with the data-action/x-action attributes
The documentation states only the "action" attribute triggers this, which is incorrect. When using
the attribute "data-action" (as for AJAX control, attempting to bypass the "action" attribute but
still make it obvious what its for), Angular thinks this is also classified as "action" and
continues with the page submission.

Closes #6196
2014-02-10 19:35:22 -05:00
Igor Minar 945fc1a4bc style(guide/concepts): remove ws 2014-02-10 16:19:10 -08:00
Sequoia McDowell ec900cabfc docs(guide/concepts): removing confusing use of hoisting
Closes #6207
2014-02-10 16:19:10 -08:00
Mark Miyashita f99fe799e2 docs(faq): add link to MIT license
Closes #6197
2014-02-10 15:58:34 -08:00
Caitlin Potter e7338d3f27 fix($compile): ensure element transclusion directives are linked with comment element
This corrects a complicated compiler issue, described in detail below:

Previously, if an element transclusion directive contained an asynchronous directive whose template
contained another element transclusion directive, the inner element transclusion directive would be
linked with the element, rather than the expected comment node.

An example manifestation of this bug would look like so:

```html
<div ng-repeat="i in [1,2,3,4,5]">
  <div my-directive>
  </div>
</div>
```

`my-directive` would be a replace directive, and its template would contain another element
transclusion directive, like so:

```html
<div ng-if="true">{{i}}</div>
```

ngIf would be linked with this template content, rather than the comment node, and the template element
would be attached to the DOM, rather than the comment. As a result, this caused ng-if to duplicate the
template when its expression evaluated to true.

Closes #6006
Closes #6101
2014-02-10 18:41:28 -05:00
Sequoia McDowell 2dfbc083c5 docs(concepts): Remove pointless * 1s
Closes #6206
2014-02-10 15:15:30 -08:00
Victor Berchet 27613fd500 docs(guide/scope): fix a typo
Signed-off-by: Caitlin Potter <caitpotter88@gmail.com>

Closes #6202
2014-02-10 18:09:03 -05:00
Julie e645f7cae1 refactor(testing): split travis end to end tests into separate jobs for jquery and jqlite
Closes #6159
2014-02-07 20:41:39 -08:00
Julie ad275b2265 refactor(doc): separate end to end tests into jquery and jqlite files 2014-02-07 20:41:11 -08:00
Julie 600e6218fe chore(testing): switch Jenkins to test e2e only on chrome
End to end tests will continue to be run on Safari and Firefox on Travis.

Closes #6187
2014-02-07 20:23:26 -08:00
jenkins 5218c7bbdc chore(release): update cdn version 2014-02-07 14:38:15 -08:00
Matias Niemelä 5cc5cc13b9 docs(changelog): release notes for 1.2.12 2014-02-07 17:00:28 -05:00
Igor Minar d5c7ef0f78 revert: refactor(mocks): simplify the inject implementation
This reverts commit 64d58a5b52.

For some weird reason this is causing regressions at Google.
I'm not sure why and I'm running out of time to investigate, so I'm taking
a safe route here and reverting the commit since it's just a refactoring.
2014-02-07 12:14:32 -08:00
Brian Ford 84fd3a18a3 docs(contributing): add code of conduct 2014-02-07 11:16:17 -08:00
Tobias Bosch fcf4393680 chore(build): Update closure i18n integration
Use git repo as source and use q-io instead of q-fs
2014-02-06 17:53:37 -08:00
Julie 16301bed28 chore(testing): de-flake a ngHref test for navigating away from the Angular page 2014-02-06 17:03:39 -08:00
Kasparas Galdikas 95be253fe5 fix($locale): Minor grammar amends for locale_lt
Closes #6164
2014-02-06 16:11:29 -08:00
asif22 bf4b0dbd46 docs(misc): fix typo in "getting started" docs
changed "building and application" to "building an application"

Closes #6156
2014-02-06 11:15:13 -05:00
sunderls 95d119ebb2 docs(injector): correct typo in example
$provide misused into $provider

maybe this should be corrected I think

Closes #6146
2014-02-06 10:44:31 -05:00
Igor Minar e609239fab chore(travis): remove double bower install to test if it's still needed
We did this due to travis-ci/travis-ci#1293 but since it's possible that this hack is not needed, I'm removing it.

If it turns out that we do need it still then we should ping the travis issue and revert this commit
2014-02-06 02:31:23 -08:00
Matias Niemelä 4224cd5182 fix(mocks): rename mock.animate to ngAnimateMock and ensure it contains all test helper code for ngAnimate
Closes #5822
Closes #5917
2014-02-06 01:22:14 -05:00
Matias Niemelä 906fdad0f9 fix(mocks): remove usage of $animate.flushNext in favour of queing
The flushNext method of testing is difficult and highly coupled with the behavior
of ngAnimate's $animate workflow. It is much better instead to just queue all
$animate animation calls into a queue collection which is available on the $animate
service when mock.animate is included as a module within test code.
2014-02-06 01:21:41 -05:00
Franziskus Domig a8c1d9c978 docs(angular.forEach): add missing space in test
Closes #6130
2014-02-05 16:25:03 -08:00
Julie 8829a2a86d chore(testing): fix Jenkins breakage due to test directory already being present 2014-02-05 16:13:06 -08:00
Julie 84467d8697 refactor(testing): run end to end tests on separate browsers in parallel 2014-02-05 15:40:16 -08:00
Julie 0e85ca9ddb chore(testing): run end to end tests on firefox and safari as well as chrome
Update the Travis and Jenkins configs to run protractor tests on Safari and Firefox as well,
and make the Travis tests run output XML and turn off color.

Fix tests which were failing in Firefox due to clear() not working as expected.

Fix tests which were failing in Safari due to SafariDriver not understanding the minus key,
and disable tests which SafariDriver has no support for.
2014-02-05 15:39:59 -08:00
Julie e7face4728 chore(end2end): remove old references to the scenario runner and update to point to protractor 2014-02-05 15:39:46 -08:00
Tobias Bosch a29bff1c98 chore(build): remove MINERR_ASSET from source map
The actual change happened in ng-closure-runner.
The change here just includes the new version.

Closes #4675
2014-02-05 15:34:56 -08:00
Hopiu 95522cc11f docs(forEach): correct spelling error
Closes #6124
2014-02-05 09:58:26 -05:00
Nicolas Leger c5f69e3f64 chore(ngdoc): fix misspellling of Naturally in sortVersionsNatrually
Corrects "sortVersionsNatrually" method name in `ngdoc.js` in "sortVersionsNaturally"
2014-02-04 16:43:39 -08:00
Daniel Luxemburg dd24c78373 fix(ngMock): return false from mock $interval.cancel() when no argument is supplied
Closes #6103.
Closed #6099.
2014-02-04 16:41:25 -08:00
Caitlin Potter 36d37c0e38 fix(jqLite): trim HTML string in jqLite constructor
jQuery will construct DOM nodes containing leading whitespace. Prior to this change, jqLite would
throw a nosel minErr due to the first character of the string not being '<'. This change corrects
this behaviour by trimming the element string in jqLite constructor before testing for '<'.

Closes #6053
2014-02-04 16:39:52 -08:00
Caitlin Potter 24699ee8f0 fix($http): ignore xhr.responseType setter exception if value is "json"
WebKit added support for the json responseType value on 09/03/2013
https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are known to throw when
setting the value "json" as the response type. Other older browsers implementing the responseType.
Other browsers with infrequent update cycles may also be affected.

The json responseType value can be ignored if not supported, because JSON payloads are parsed on the
client-side regardless.

Closes #6115
Closes #6122
2014-02-04 19:34:31 -05:00
GiffenGood aa6a0e3fc6 docs(log.js): param debugEnabled is a boolean and not a string 2014-02-04 16:26:36 -08:00
Tobias Bosch 8761ddc0e3 chore(release): be able to release any commit
The version information is now stored only in the tags.
By this we are able to release commits in the past, which
have already been tested, so we don't need a code freeze
or run tests any more. This is also the first step for
letting Travis do the releases in the future.

The package.json now contains the new
property 'branchVersion' that defines which tags are
valid on this branch.

Closes #6116
2014-02-04 16:20:22 -08:00
Caitlin Potter 058842ad04 revert: "fix($http): ignore xhr.responseType setter exception if value is "json""
This reverts commit 431bad0183.
2014-02-04 19:09:53 -05:00
Caitlin Potter 431bad0183 fix($http): ignore xhr.responseType setter exception if value is "json"
WebKit added support for the json responseType value on 09/03/2013
https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are known to throw when
setting the value "json" as the response type. Other older browsers implementing the responseType.
Other browsers with infrequent update cycles may also be affected.

The json responseType value can be ignored if not supported, because JSON payloads are parsed on the
client-side regardless.

Closes #6115
Closes #6122
2014-02-04 18:46:54 -05:00
Caitlin Potter 5850e61c82 docs(CHANGELOG): add breaking change notice for 1.2.9 $http default headers change
Add a mention for the slightly breaking change introduced in 1.2.9.

Closes #6022
2014-02-04 10:59:44 -05:00
Thomas Belin d2e4e49986 fix(ngResource): don't filter "$"-prefixed properties from ngResource requests/responses
ngResource no longer filters properties prefixed with a single "$" character from requests or
responses, correcting a regression introduced in 1.2.6 (cb29632a) which caused shallowCopy and
shallowClearAndCopy to ignore properties prefixed with a single "$".

Closes #5666
Closes #6080
Closes #6033
2014-02-04 10:51:24 -05:00
Kamil Pekala 0da6cc9118 docs($compile): fixed syntax error.
"how to" was written twice in a row.

Closes #6110
2014-02-04 08:45:48 -05:00
John Kurlak cc60ba1f35 docs($q): fixed grammatical error
"Promises" should be of the possessive form.

Closes #6082
2014-02-04 00:00:31 -08:00
Igor Minar 64d58a5b52 refactor(mocks): simplify the implementation 2014-02-03 22:41:18 -08:00
Wes Alvaro 3bf4390339 fix(mocks): always call functions injected with inject with this set to the current spec
Currently when a function is injected inside of a test we set the context to undefined which
is a bug.

Closes #6102
2014-02-03 22:25:30 -08:00
gdi2290 e7ac7aa43b chore(Gruntfile.js, package.json): use load-grunt-tasks and move grunt-contrib-jshint into devDependencies
Closes #6085
2014-02-03 19:25:47 -05:00
PatrickJS 37781ed145 style(License): update copyright year
Closes #6090
2014-02-03 19:19:29 -05:00
Igor Minar 19ba6510d0 chore(ngClass): remove debugger statement from an e2e test 2014-02-03 11:43:14 -08:00
jenkins bc5ceee275 chore(release): update cdn version 2014-02-03 10:03:06 -08:00
jenkins 106af49258 chore(release): start v1.2.12 (1.2.12) 2014-02-03 09:41:17 -08:00
jenkins d5eedf38db chore(release): cut v1.2.11 release 2014-02-03 09:40:03 -08:00
Tobias Bosch c0c0b2b674 docs(changelog): release notes for 1.2.11 2014-02-03 09:28:45 -08:00
Caitlin Potter ce1f1f97f0 fix(ngResource): don't append number to '$' in url param value when encoding URI
Previously, if a URL parameter value included a $, it would replace the dollar sign with a literal
'$1' for mysterious reasons. Using a function rather than a replacement string circumvents this
behaviour and produces a more expected result.

Closes #6003
Closes #6004
2014-02-03 11:12:07 -05:00
Alan Rubin 8205158e47 docs(animations): ngClass usage should link to ngClass documentation
Fixing reference to ngClass documentation

Closes #6089
2014-02-03 09:14:45 -05:00
Igor Minar 6609e3da76 fix(http): make jshint happy 2014-01-31 17:03:30 -08:00
Jorg ef210e5e11 fix($http): update httpBackend to use ActiveXObject on IE8 if necessary
window.XMLHttpRequest is not always available in IE8 despite it not running in quirks mode,
in which case Angular should be using the ActiveXObject instead. Just checking the browser
version is taking too many shortcuts.

Closes #5677
Closes #5679
2014-01-31 16:52:42 -08:00
Stéphane Reynaud fd61e222c3 docs(guide/templates.css-styling): fix "{{}}"
Replace {{}} to `{{}}` so that "{{}}" is rendered in the HTML page

Closes #5950
2014-01-31 16:31:06 -08:00
Lucas Galfasó 074b0675a1 fix($q): make $q.reject support finally and catch
Add support for the functions `finally` and `catch` to the
promise returned by `$q.reject`

Closes #6048
Closes #6076
2014-01-31 14:38:06 -05:00
Caitlin Potter 5ed721b9b5 fix($compile): retain CSS classes added in cloneAttachFn on asynchronous directives
Previously, classes added to asynchronous directive elements during the clone
attach function would not persist after the node is merged with the template, prior
to linking. This change corrects this behaviour and brings it in line with synchronous
directives.

Closes #5439
Closes #5617
2014-01-31 12:45:35 -05:00
Vojta Jina c22ab5d2e2 chore(travis): remove BrowserStack credentials
Closes #5596
2014-01-31 07:48:36 -08:00
Igor Minar 339a1658cd fix(filterFilter): don't interpret dots in predicate object fields as paths
Closes #6005
Closes #6009
2014-01-31 03:16:46 -08:00
Shawn Flahave 29432ffe37 Edited the 'Flushing HTTP requests' section
Minor grammatical edits in the Flushing HTTP requests section.
2014-01-30 16:52:06 -08:00
Brad Williams caed2dfe4f fix(docs): clarify doc for "args" in $broadcast and $emit
Closes #6047.
2014-01-30 16:50:08 -08:00
Igor Minar 7f4edaff6e chore(build): add jscs code style check to our build
Closes #6062
2014-01-30 11:39:53 -08:00
Igor Minar 6dcfccb32c chore(grunt): sort the grunt task load statements 2014-01-30 10:51:20 -08:00
Clark Pan e2173f9101 docs($provide): return instance of Ping, rather than constructor
Updating $provide.service method docs

The previous example provided for the service method did not work.  I've updated the example to a working example.

I think this version of the example will probably make more sense to most people, and the factory method would be
a better place for this sort of example.

Closes #6008
2014-01-29 09:10:01 -05:00
Manan 7a78aed160 docs(compile/nodomevents): fix typo ng-mode -> ng-model
correcting directive name to ng-model instead of ng-mode in nodomevents.ngdoc

Correcting use of ng-model directive

Closes #6036
2014-01-29 08:51:02 -05:00
Julie 7aef2d54e0 test(docs): convert example end to end doc tests from scenario runner to protractor
Thanks to jeffbcross, petebacondarwin, btford, jdeboer, tbosch for contributions!

Closes #6023
2014-01-28 14:14:20 -05:00
Julie ce37ae2868 docs(cookbook): remove the cookbook docs
The cookbook docs are now superceded by the guide. They are no longer available
in any menus and the only way to find them is to search for them. Remove!

Closes #5967
2014-01-28 14:12:52 -05:00
John Papa 95f0bf9b52 fix(mocks): refactor currentSpec to work w/ Jasmine 2
Closes #5662
2014-01-27 22:26:18 -08:00
Igor Minar 8a0be355a9 docs(triaging.md): update the 'PRs plz!' label name 2014-01-27 21:17:53 -08:00
Igor Minar 849a1489c7 style(triaging.md): removing ws 2014-01-27 12:06:17 -08:00
Brian Ford f54f0f98a0 chore(docs): remove note about removing disqus 2014-01-27 11:46:18 -08:00
David Nelson 766b3d5c87 docs(select): rephase note on ngOptions
added 'an' and 'the' to ngOptions sentence

Closes #5993
2014-01-26 15:45:04 -05:00
David Nelson 4f735b0605 docs(ngSwitch): fixed typo 'choses' to 'chooses'
Closes #5992
2014-01-26 15:42:27 -05:00
jenkins 319dd1a449 chore(release): update cdn version 2014-01-24 19:27:18 -08:00
jenkins 88a14b4e25 chore(release): start v1.2.11 (1.2.11) 2014-01-24 15:42:49 -08:00
123 changed files with 2504 additions and 2162 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"disallowKeywords": ["with"]
}
+22
View File
@@ -0,0 +1,22 @@
// This is an incomplete TODO list of checks we want to start enforcing
//
// The goal is to enable these checks one by one by moving them to .jscs.json along with commits
// that correct the existing code base issues and make the new check pass.
{
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"disallowRightStickedOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"requireRightStickedOperators": ["!"],
"requireLeftStickedOperators": [","],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowKeywordsOnNewLine": ["else"],
"disallowTrailingWhitespace": true,
"requireLineFeedAtFileEnd": true,
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
}
}
+7 -6
View File
@@ -5,12 +5,15 @@ node_js:
env:
matrix:
- JOB=unit
- JOB=e2e
- JOB=e2e BROWSER=chrome JQVERSION=jqlite
- JOB=e2e BROWSER=firefox JQVERSION=jqlite
- JOB=e2e BROWSER=safari JQVERSION=jqlite
- JOB=e2e BROWSER=chrome JQVERSION=jquery
- JOB=e2e BROWSER=firefox JQVERSION=jquery
- JOB=e2e BROWSER=safari JQVERSION=jquery
global:
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
- BROWSER_STACK_USERNAME=VojtaJina
- BROWSER_STACK_ACCESS_KEY=HAfHZaypxAc3PEUrUU9a
- LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
@@ -18,9 +21,7 @@ before_script:
- mkdir -p $LOGS_DIR
- ./lib/sauce/sauce_connect_setup.sh
- npm install -g grunt-cli
- grunt bower
- grunt bower
- grunt package-without-bower
- grunt package
- ./scripts/travis/wait_for_browser_provider.sh
script:
+164
View File
@@ -1,3 +1,143 @@
<a name="1.2.13"></a>
# 1.2.13 romantic-transclusion (2014-02-14)
## Bug Fixes
- **$animate:** ensure $animate doesn't break natural CSS transitions
([4f84f6b3](https://github.com/angular/angular.js/commit/4f84f6b3e4210ae1eb14728a46d43dd961700a0c),
[#6019](https://github.com/angular/angular.js/issues/6019))
- **$compile:**
- ensure element transclusion directives are linked with comment element
([e7338d3f](https://github.com/angular/angular.js/commit/e7338d3f27e8824196136a18e1c3e0fcf51a0e28),
[#6006](https://github.com/angular/angular.js/issues/6006), [#6101](https://github.com/angular/angular.js/issues/6101))
- support templates with table content root nodes
([e7338d3f](https://github.com/angular/angular.js/commit/31c450bcee53d0a3827b7e0a611e9013b2496506),
[#2848](https://github.com/angular/angular.js/issues/2848), [#1459](https://github.com/angular/angular.js/issues/1459), [#3647](https://github.com/angular/angular.js/issues/3647), [#3241](https://github.com/angular/angular.js/issues/3241))
- **input:**
- don't apply textInput to `<input type="file">`
([a9fcb0d0](https://github.com/angular/angular.js/commit/a9fcb0d0fc6456f80501b8820d02b04d7c15b6d6),
[#6247](https://github.com/angular/angular.js/issues/6247), [#6231](https://github.com/angular/angular.js/issues/6231))
- setViewValue on compositionend
([2b730271](https://github.com/angular/angular.js/commit/2b7302713674506fdbcdc396c38f18dcb90dee8c),
[#6058](https://github.com/angular/angular.js/issues/6058), [#5433](https://github.com/angular/angular.js/issues/5433))
## Features
- **filterFilter:** support deeply nested predicate objects
([b4eed8ad](https://github.com/angular/angular.js/commit/b4eed8ad94ce9719540462c1ee969dfd3c6b2355),
[#6215](https://github.com/angular/angular.js/issues/6215))
## Breaking Changes
- **$animate:**
- due to [4f84f6b3](https://github.com/angular/angular.js/commit/4f84f6b3e4210ae1eb14728a46d43dd961700a0c),
ngClass and {{ class }} will now call the `setClass`
animation callback instead of addClass / removeClass when both a
addClass/removeClass operation is being executed on the element during the animation.
Please include the setClass animation callback as well as addClass and removeClass within
your JS animations to work with ngClass and {{ class }} directives.
- due to [cf5e463a](https://github.com/angular/angular.js/commit/cf5e463abd2c23f62e9c2e6361e6c53048c8910e),
Both the `$animate:before` and `$animate:after` DOM events must be now
registered prior to the $animate operation taking place. The `$animate:close` event
can be registered anytime afterwards.
DOM callbacks used to fired for each and every animation operation that occurs within the
$animate service provided in the ngAnimate module. This may end up slowing down an
application if 100s of elements are being inserted into the page. Therefore after this
change callbacks are only fired if registered on the element being animated.
<a name="1.2.12"></a>
# 1.2.12 cauliflower-eradication (2014-02-07)
## Bug Fixes
- **$compile:** retain CSS classes added in cloneAttachFn on asynchronous directives
([5ed721b9](https://github.com/angular/angular.js/commit/5ed721b9b5e95ae08450e1ae9d5202e7f3f79295),
[#5439](https://github.com/angular/angular.js/issues/5439), [#5617](https://github.com/angular/angular.js/issues/5617))
- **$http:**
- ignore xhr.responseType setter exception if value is "json"
([24699ee8](https://github.com/angular/angular.js/commit/24699ee8f04c1f1459be1d36207e654421d58ff0),
[#6115](https://github.com/angular/angular.js/issues/6115), [#6122](https://github.com/angular/angular.js/issues/6122))
- update httpBackend to use ActiveXObject on IE8 if necessary
([ef210e5e](https://github.com/angular/angular.js/commit/ef210e5e119db4f5bfc9d2428b19f9b335c4f976),
[#5677](https://github.com/angular/angular.js/issues/5677), [#5679](https://github.com/angular/angular.js/issues/5679))
- **$locale:** minor grammar amends for the locale `locale_lt`
([95be253f](https://github.com/angular/angular.js/commit/95be253fe55d35336d425d3d600a36158fc3519d),
[#6164](https://github.com/angular/angular.js/issues/6164))
- **$q:** make $q.reject support `finally` and `catch`
([074b0675](https://github.com/angular/angular.js/commit/074b0675a1f97dce07f520f1ae6198ed3c604000),
[#6048](https://github.com/angular/angular.js/issues/6048), [#6076](https://github.com/angular/angular.js/issues/6076))
- **docs:** clarify doc for "args" in $broadcast and $emit
([caed2dfe](https://github.com/angular/angular.js/commit/caed2dfe4feeac5d19ecea2dbb1456b7fde21e6d),
[#6047](https://github.com/angular/angular.js/issues/6047))
- **filterFilter:** don't interpret dots in predicate object fields as paths
([339a1658](https://github.com/angular/angular.js/commit/339a1658cd9bfa5e322a01c45aa0a1df67e3a842),
[#6005](https://github.com/angular/angular.js/issues/6005), [#6009](https://github.com/angular/angular.js/issues/6009))
- **http:** make jshint happy
([6609e3da](https://github.com/angular/angular.js/commit/6609e3da76dd898cfe85f75f23ab2e39fee65fe5))
- **jqLite:** trim HTML string in jqLite constructor
([36d37c0e](https://github.com/angular/angular.js/commit/36d37c0e3880c774d20c014ade60d2331beefa15),
[#6053](https://github.com/angular/angular.js/issues/6053))
- **mocks:**
- rename mock.animate to ngAnimateMock and ensure it contains all test helper code for ngAnimate
([4224cd51](https://github.com/angular/angular.js/commit/4224cd5182bc93e4a210f75e0a4e4de7f3c544e8),
[#5822](https://github.com/angular/angular.js/issues/5822), [#5917](https://github.com/angular/angular.js/issues/5917))
- remove usage of $animate.flushNext in favour of queing
([906fdad0](https://github.com/angular/angular.js/commit/906fdad0f95465842e336e057ea97d0633712189))
- always call functions injected with `inject` with `this` set to the current spec
([3bf43903](https://github.com/angular/angular.js/commit/3bf43903397c703aa2e9ba1e1a48dbc9e8286ee2),
[#6102](https://github.com/angular/angular.js/issues/6102))
- refactor currentSpec to work w/ Jasmine 2
([95f0bf9b](https://github.com/angular/angular.js/commit/95f0bf9b526fda8964527c6d4aef1ad50a47f1f3),
[#5662](https://github.com/angular/angular.js/issues/5662))
- **ngMock:** return false from mock $interval.cancel() when no argument is supplied
([dd24c783](https://github.com/angular/angular.js/commit/dd24c78373b5d24ecb3b9d19e61e1b3b6c74d155),
[#6103](https://github.com/angular/angular.js/issues/6103))
- **ngResource:**
- don't filter "$"-prefixed properties from ngResource requests/responses
([d2e4e499](https://github.com/angular/angular.js/commit/d2e4e499862aeca157dbe7a7422c465e7c79205e),
[#5666](https://github.com/angular/angular.js/issues/5666), [#6080](https://github.com/angular/angular.js/issues/6080), [#6033](https://github.com/angular/angular.js/issues/6033))
- don't append number to '$' in url param value when encoding URI
([ce1f1f97](https://github.com/angular/angular.js/commit/ce1f1f97f0ebf77941b2bdaf5e8352d33786524d),
[#6003](https://github.com/angular/angular.js/issues/6003), [#6004](https://github.com/angular/angular.js/issues/6004))
## Breaking Changes
The animation mock module has been renamed from `mock.animate` to `ngAnimateMock`. In addition to the rename, animations will not block within test code even when ngAnimateMock is used. However, all function calls to $animate will be recorded into `$animate.queue` and are available within test code to assert animation calls. In addition, `$animate.triggerReflow()` is now only available when `ngAnimateMock` is used.
<a name="1.2.11"></a>
# 1.2.11 cryptocurrency-hyperdeflation (2014-02-03)
## Bug Fixes
- **$compile:** retain CSS classes added in cloneAttachFn on asynchronous directives
([5ed721b9](https://github.com/angular/angular.js/commit/5ed721b9b5e95ae08450e1ae9d5202e7f3f79295),
[#5439](https://github.com/angular/angular.js/issues/5439), [#5617](https://github.com/angular/angular.js/issues/5617))
- **$http:** update httpBackend to use ActiveXObject on IE8 if necessary
([ef210e5e](https://github.com/angular/angular.js/commit/ef210e5e119db4f5bfc9d2428b19f9b335c4f976),
[#5677](https://github.com/angular/angular.js/issues/5677), [#5679](https://github.com/angular/angular.js/issues/5679))
- **$q:** make $q.reject support `finally` and `catch`
([074b0675](https://github.com/angular/angular.js/commit/074b0675a1f97dce07f520f1ae6198ed3c604000),
[#6048](https://github.com/angular/angular.js/issues/6048), [#6076](https://github.com/angular/angular.js/issues/6076))
- **filterFilter:** don't interpret dots in predicate object fields as paths
([339a1658](https://github.com/angular/angular.js/commit/339a1658cd9bfa5e322a01c45aa0a1df67e3a842),
[#6005](https://github.com/angular/angular.js/issues/6005), [#6009](https://github.com/angular/angular.js/issues/6009))
- **mocks:** refactor currentSpec to work w/ Jasmine 2
([95f0bf9b](https://github.com/angular/angular.js/commit/95f0bf9b526fda8964527c6d4aef1ad50a47f1f3),
[#5662](https://github.com/angular/angular.js/issues/5662))
- **ngResource:** don't append number to '$' in url param value when encoding URI
([ce1f1f97](https://github.com/angular/angular.js/commit/ce1f1f97f0ebf77941b2bdaf5e8352d33786524d),
[#6003](https://github.com/angular/angular.js/issues/6003), [#6004](https://github.com/angular/angular.js/issues/6004))
<a name="1.2.10"></a>
# 1.2.10 augmented-serendipity (2014-01-24)
@@ -58,6 +198,30 @@
([4ae3184c](https://github.com/angular/angular.js/commit/4ae3184c5915aac9aa00889aa2153c8e84c14966),
[#4278](https://github.com/angular/angular.js/issues/4278), [#4225](https://github.com/angular/angular.js/issues/4225))
## Breaking Changes
- **$http:** due to [e1cfb195](https://github.com/angular/angular.js/commit/e1cfb1957feaf89408bccf48fae6f529e57a82fe),
it is now necessary to seperately specify default HTTP headers for PUT, POST and PATCH requests, as these no longer share a single object.
To migrate your code, follow the example below:
Before:
// Will apply to POST, PUT and PATCH methods
$httpProvider.defaults.headers.post = {
"X-MY-CSRF-HEADER": "..."
};
After:
// POST, PUT and PATCH default headers must be specified seperately,
// as they do not share data.
$httpProvider.defaults.headers.post =
$httpProvider.defaults.headers.put =
$httpProviders.defaults.headers.patch = {
"X-MY-CSRF-HEADER": "..."
};
<a name="1.2.8"></a>
# 1.2.8 interdimensional-cartography (2014-01-10)
+4
View File
@@ -3,6 +3,9 @@
We'd love for you to contribute to our source code and to make AngularJS even better than it is
today! Here are the guidelines we'd like you to follow:
## Code of Conduct
Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct][coc].
## Got a Question or Problem?
If you have questions about how to use AngularJS, please direct these to the [Google Group][groups]
@@ -258,5 +261,6 @@ You can find out more detailed information about contributing in the
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
[github-pr-helper]: https://chrome.google.com/webstore/detail/github-pr-helper/mokbklfnaddkkbolfldepnkfmanfhpen
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/CONTRIBUTING.md?pixel)](https://github.com/igrigorik/ga-beacon)
+20 -22
View File
@@ -4,17 +4,8 @@ var path = require('path');
module.exports = function(grunt) {
//grunt plugins
grunt.loadNpmTasks('grunt-bump');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-jasmine-node');
grunt.loadNpmTasks('grunt-ddescribe-iit');
grunt.loadNpmTasks('grunt-merge-conflict');
grunt.loadNpmTasks('grunt-parallel');
grunt.loadNpmTasks('grunt-shell');
grunt.loadNpmTasks('grunt-contrib-jshint');
require('load-grunt-tasks')(grunt);
grunt.loadTasks('lib/grunt');
var NG_VERSION = util.getVersion();
@@ -87,9 +78,7 @@ module.exports = function(grunt) {
jqlite: 'karma-jqlite.conf.js',
jquery: 'karma-jquery.conf.js',
docs: 'karma-docs.conf.js',
modules: 'karma-modules.conf.js',
//NOTE run grunt test:e2e instead and it will start a webserver for you
end2end: 'karma-e2e.conf.js'
modules: 'karma-modules.conf.js'
},
@@ -101,8 +90,10 @@ module.exports = function(grunt) {
},
runprotractor: {
normal: 'protractor-conf.js'
protractor: {
normal: 'protractor-conf.js',
jquery: 'protractor-jquery-conf.js',
jenkins: 'protractor-jenkins-conf.js'
},
@@ -147,6 +138,13 @@ module.exports = function(grunt) {
}
},
jscs: {
src: ['src/**/*.js', 'test/**/*.js'],
options: {
config: ".jscs.json"
}
},
build: {
scenario: {
dest: 'build/angular-scenario.js',
@@ -288,22 +286,22 @@ module.exports = function(grunt) {
//alias tasks
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:e2e', 'webdriver', 'runprotractor:normal']);
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']);
grunt.registerTask('test:e2e', 'Run the end to end tests with Karma and keep a test server running in the background', ['connect:testserver', 'tests:end2end']);
// This should eventually replace test:e2e
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'runprotractor:normal']);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
grunt.registerTask('test:jq-protractor', 'Run the end to end tests against jquery with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:jquery']);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
grunt.registerTask('test:e2e', 'Alias for test:protractor', ['test:protractor']);
grunt.registerTask('test:docgen', ['jasmine_node']);
grunt.registerTask('test:promises-aplus',['build:promises-aplus-adapter','shell:promises-aplus-tests']);
grunt.registerTask('minify', ['bower','clean', 'build', 'minall']);
grunt.registerTask('webserver', ['connect:devserver']);
grunt.registerTask('package', ['bower','clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
grunt.registerTask('package-without-bower', ['clean', 'buildall', 'minall', 'collect-errors', 'docs', 'copy', 'write', 'compress']);
grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'jshint']);
grunt.registerTask('ci-checks', ['ddescribe-iit', 'merge-conflict', 'jshint', 'jscs']);
grunt.registerTask('default', ['package']);
};
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2010-2012 Google, Inc. http://angularjs.org
Copyright (c) 2010-2014 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+3 -3
View File
@@ -27,7 +27,7 @@ The following is done automatically and should not be done manually:
* 1.2.x - everything else
1. Label "GH: *" (to be automated via Mary Poppins)
* PR - issue is a PR
* issue - otherwise
* issue - otherwise
1. Bugs:
* Label "Type: Bug"
* Label "Type: Regression" - if the bug is a regression
@@ -43,7 +43,7 @@ The following is done automatically and should not be done manually:
* Goals of angular core? - Often new features should be implemented as a third-party module rather than an addition to the core.
1. Label "component: *"
* In rare cases, it's ok to have multiple components.
* In rare cases, it's ok to have multiple components.
1. Label "impact: *"
* small - obscure issue affecting one or handful of developers
* medium - impacts some usage patterns
@@ -52,7 +52,7 @@ The following is done automatically and should not be done manually:
* small - trivial change
* medium - non-trivial but straightforward change
* large - changes to many components in angular or any changes to $compile, ngRepeat or other "fun" components
1. Label "PRs welcome" for "GH: issue"
1. Label "PRs plz!" for "GH: issue"
* if complexity is small or medium and the problem as well as solution are well captured in the issue
1. Label "origin: google" for issues from Google
1. Label "high priority" for security issues, major performance regressions or memory leaks
+1 -1
View File
@@ -7,6 +7,6 @@
"components-font-awesome": "3.1.0",
"bootstrap": "https://raw.github.com/twbs/bootstrap/v2.0.2/docs/assets/bootstrap.zip",
"closure-compiler": "https://closure-compiler.googlecode.com/files/compiler-20130603.zip",
"ng-closure-runner": "https://raw.github.com/angular/ng-closure-runner/v0.2.2/assets/ng-closure-runner.zip"
"ng-closure-runner": "https://raw.github.com/angular/ng-closure-runner/v0.2.3/assets/ng-closure-runner.zip"
}
}
+5
View File
@@ -9,3 +9,8 @@
ng\:form {
display: block;
}
.ng-animate-block-transitions {
transition:0s all!important;
-webkit-transition:0s all!important;
}
+3 -9
View File
@@ -13,8 +13,7 @@ describe("docsSearch", function() {
results[0] = { section: 'tutorial', shortName: 'item one', keywords: 'item, one, 1' };
results[1] = { section: 'tutorial', shortName: 'item man', keywords: 'item, man' };
results[2] = { section: 'api', shortName: 'item other', keywords: 'item, other' };
results[3] = { section: 'cookbook', shortName: 'item cookbook', keywords: 'item, other' };
results[4] = { section: 'api', shortName: 'ngRepeat', keywords: 'item, other' };
results[3] = { section: 'api', shortName: 'ngRepeat', keywords: 'item, other' };
$provide.value('NG_PAGES', results);
$provide.factory('lunrSearch', function() {
@@ -41,19 +40,14 @@ describe("docsSearch", function() {
expect(items['api'].length).toBe(2);
}));
it("should place cookbook items in the tutorial", inject(function(docsSearch) {
var items = docsSearch('item');
expect(items['tutorial'].length).toBe(3);
}));
it("should return all results without a search", inject(function(docsSearch) {
var items = docsSearch();
expect(items['tutorial'].length).toBe(3);
expect(items['tutorial'].length).toBe(2);
expect(items['api'].length).toBe(2);
}));
it("should store values with and without a ng prefix", inject(function(docsSearch) {
expect(interceptedLunrResults[4].title).toBe('ngRepeat repeat');
expect(interceptedLunrResults[3].title).toBe('ngRepeat repeat');
}));
});
-122
View File
@@ -1,122 +0,0 @@
@ngdoc overview
@name Cookbook: Advanced Form
@description
Here we extend the basic form example to include common features such as reverting, dirty state
detection, and preventing invalid form submission.
<doc:example>
<doc:source>
<script>
function UserForm($scope) {
var master = {
name: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
},
contacts:[
{type:'phone', value:'1(234) 555-1212'}
]
};
$scope.state = /^\w\w$/;
$scope.zip = /^\d\d\d\d\d$/;
$scope.cancel = function() {
$scope.form = angular.copy(master);
};
$scope.save = function() {
master = $scope.form;
$scope.cancel();
};
$scope.addContact = function() {
$scope.form.contacts.push({type:'', value:''});
};
$scope.removeContact = function(index) {
$scope.form.contacts.splice(index, 1);
};
$scope.isCancelDisabled = function() {
return angular.equals(master, $scope.form);
};
$scope.isSaveDisabled = function() {
return $scope.myForm.$invalid || angular.equals(master, $scope.form);
};
$scope.cancel();
}
</script>
<div ng-controller="UserForm">
<form name="myForm">
<label>Name:</label><br/>
<input type="text" ng-model="form.name" required/> <br/><br/>
<label>Address:</label> <br/>
<input type="text" ng-model="form.address.line1" size="33" required/> <br/>
<input type="text" ng-model="form.address.city" size="12" required/>,
<input type="text" ng-model="form.address.state" size="2"
ng-pattern="state" required/>
<input type="text" ng-model="form.address.zip" size="5"
ng-pattern="zip" required/><br/><br/>
<label>Contacts:</label>
[ <a href="" ng-click="addContact()">add</a> ]
<div ng-repeat="contact in form.contacts">
<select ng-model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng-model="contact.value" required/>
[ <a href="" ng-click="removeContact($index)">X</a> ]
</div>
<button ng-click="cancel()" ng-disabled="isCancelDisabled()">Cancel</button>
<button ng-click="save()" ng-disabled="isSaveDisabled()">Save</button>
</form>
<hr/>
Debug View:
<pre>form={{form}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function() {
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('');
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function() {
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
element(':button:contains(Cancel)').click();
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
});
</doc:scenario>
</doc:example>
#Things to notice
* Cancel & save buttons are only enabled if the form is dirty — there is something to cancel or
save.
* Save button is only enabled if there are no validation errors on the form.
* Cancel reverts the form changes back to original state.
* Save updates the internal model of the form.
* Debug view shows the two models. One presented to the user form and the other being the pristine
copy master.
-63
View File
@@ -1,63 +0,0 @@
@ngdoc overview
@name Cookbook: Resources - Buzz
@description
External resources are URLs that provide JSON data, which are then rendered with the help of
templates. Angular has a resource factory that can be used to give names to the URLs and then
attach behavior to them. For example you can use the
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz
API}
to retrieve Buzz activity and comments.
<doc:example>
<doc:source>
<script>
BuzzController.$inject = ['$scope', '$resource'];
function BuzzController($scope, $resource) {
$scope.userId = 'googlebuzz';
$scope.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'}}
});
$scope.fetch = function() {
$scope.activities = $scope.Activity.get({userId:this.userId});
}
$scope.expandReplies = function(activity) {
activity.replies = $scope.Activity.replies({userId: this.userId, activityId: activity.id});
}
};
</script>
<div ng-controller="BuzzController">
<input ng-model="userId"/>
<button ng-click="fetch()">fetch</button>
<hr/>
<div class="buzz" ng-repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img ng-src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng-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 class="reply" ng-repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img ng-src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a ng-href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
{{reply.content | html}}
</div>
</div>
</div>
</doc:source>
<doc:scenario>
xit('fetch buzz and expand', function() {
element(':button:contains(fetch)').click();
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
element('.buzz a:contains(Expand replies):first').click();
expect(repeater('div.reply').count()).toBeGreaterThan(0);
});
</doc:scenario>
</doc:example>
-151
View File
@@ -1,151 +0,0 @@
@ngdoc overview
@name Cookbook: Deep Linking
@description
Deep linking allows you to encode the state of the application in the URL so that it can be
bookmarked and the application can be restored from the URL to the same state.
While Angular does not force you to deal with bookmarks in any particular way, it has services
which make the common case described here very easy to implement.
# Assumptions
Your application consists of a single HTML page which bootstraps the application. We will refer
to this page as the chrome.
Your application is divided into several screens (or views) which the user can visit. For example,
the home screen, settings screen, details screen, etc. For each of these screens, we would like to
assign a URL so that it can be bookmarked and later restored. Each of these screens will be
associated with a controller which define the screen's behavior. The most common case is that the
screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
have multiple partials, but a single partial is the most common construct. This example makes the
partial boundary visible using a blue line.
You can make a routing table which shows which URL maps to which partial view template and which
controller.
# Example
In this example we have a simple app which consist of two screens:
* Welcome: url `welcome` Show the user contact information.
* Settings: url `settings` Show an edit screen for user contact information.
<example module="deepLinking" deps="angular-route.js, angular-sanitize.js">
<file name="script.js">
angular.module('deepLinking', ['ngRoute', 'ngSanitize'])
.config(function($routeProvider) {
$routeProvider.
when("/welcome", {templateUrl:'welcome.html', controller:WelcomeCntl}).
when("/settings", {templateUrl:'settings.html', controller:SettingsCntl});
});
AppCntl.$inject = ['$scope', '$route']
function AppCntl($scope, $route) {
$scope.$route = $route;
// initialize the model to something useful
$scope.person = {
name:'anonymous',
contacts:[{type:'email', url:'anonymous@example.com'}]
};
}
function WelcomeCntl($scope) {
$scope.greet = function() {
alert("Hello " + $scope.person.name);
};
}
function SettingsCntl($scope, $location) {
$scope.cancel = function() {
$scope.form = angular.copy($scope.person);
};
$scope.save = function() {
angular.copy($scope.form, $scope.person);
$location.path('/welcome');
};
$scope.cancel();
}
</file>
<file name="style.css">
[ng-view] {
border: 1px solid blue;
margin: 0;
padding:1em;
}
.partial-info {
background-color: blue;
color: white;
padding: 3px;
}
</file>
<file name="index.html">
<div ng-controller="AppCntl">
<h1>Your App Chrome</h1>
[ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
<hr/>
<span class="partial-info">
Partial: {{$route.current.template}}
</span>
<div ng-view></div>
<small>Your app footer </small>
</div>
</file>
<file name="settings.html">
<label>Name:</label>
<input type="text" ng:model="form.name" required>
<div ng:repeat="contact in form.contacts">
<select ng:model="contact.type">
<option>url</option>
<option>email</option>
<option>phone</option>
</select>
<input type="text" ng:model="contact.url">
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<div>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
</div>
<button ng:click="cancel()">Cancel</button>
<button ng:click="save()">Save</button>
</file>
<file name="welcome.html">
Hello {{person.name}},
<div>
Your contact information:
<div ng:repeat="contact in person.contacts">{{contact.type}}:
<span ng-bind-html="contact.url|linky"></span>
</div>
</div>
</file>
<file name="scenario.js">
it('should navigate to URL', function() {
element('a:contains(Welcome)').click();
expect(element('[ng-view]').text()).toMatch(/Hello anonymous/);
element('a:contains(Settings)').click();
input('form.name').enter('yourname');
element(':button:contains(Save)').click();
element('a:contains(Welcome)').click();
expect(element('[ng-view]').text()).toMatch(/Hello yourname/);
});
</file>
</example>
# Things to notice
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
initialization of the {@link api/ngRoute.$route $route} service with the proper URL
routes.
* The {@link api/ngRoute.$route $route} service then watches the URL and instantiates the
appropriate controller when the URL changes.
* The {@link api/ngRoute.directive:ngView ngView} widget loads the
view when the URL changes. It also sets the view scope to the newly instantiated controller.
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
the URL is changed programmatically or by the user.
-114
View File
@@ -1,114 +0,0 @@
@ngdoc overview
@name Cookbook: Form
@description
A web application's main purpose is to present and gather data. For this reason Angular strives
to make both of these operations trivial. This example shows off how you can build a simple form to
allow a user to enter data.
<doc:example>
<doc:source>
<script>
function FormController($scope) {
var user = $scope.user = {
name: 'John Smith',
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
contacts:[{type:'phone', value:'1(234) 555-1212'}]
};
$scope.state = /^\w\w$/;
$scope.zip = /^\d\d\d\d\d$/;
$scope.addContact = function() {
user.contacts.push({type:'email', value:''});
};
$scope.removeContact = function(contact) {
for (var i = 0, ii = user.contacts.length; i < ii; i++) {
if (contact === user.contacts[i]) {
$scope.user.contacts.splice(i, 1);
}
}
};
}
</script>
<div ng-controller="FormController" class="example">
<label>Name:</label><br>
<input type="text" ng-model="user.name" required/> <br><br>
<label>Address:</label><br>
<input type="text" ng-model="user.address.line1" size="33" required> <br>
<input type="text" ng-model="user.address.city" size="12" required>,
<input type="text" ng-model="user.address.state"
ng-pattern="state" size="2" required>
<input type="text" ng-model="user.address.zip" size="5"
ng-pattern="zip" required><br><br>
<label>Phone:</label>
[ <a href="" ng-click="addContact()">add</a> ]
<div ng-repeat="contact in user.contacts">
<select ng-model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng-model="contact.value" required>
[ <a href="" ng-click="removeContact(contact)">X</a> ]
</div>
<hr/>
Debug View:
<pre>user={{user | json}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should show debug', function() {
expect(binding('user')).toMatch(/John Smith/);
});
it('should add contact', function() {
using('.example').element('a:contains(add)').click();
using('.example div:last').input('contact.value').enter('you@example.org');
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
expect(binding('user')).toMatch(/you@example.org/);
});
it('should remove contact', function() {
using('.example').element('a:contains(X)').click();
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
});
it('should validate zip', function() {
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).not().toMatch(/ng-invalid/);
using('.example').input('user.address.zip').enter('abc');
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).toMatch(/ng-invalid/);
});
it('should validate state', function() {
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.not().toMatch(/ng-invalid/);
using('.example').input('user.address.state').enter('XXX');
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.toMatch(/ng-invalid/);
});
</doc:scenario>
</doc:example>
# Things to notice
* The user data model is initialized {@link api/ng.directive:ngController controller} and is
available in the {@link api/ng.$rootScope.Scope scope} with the initial data.
* For debugging purposes we have included a debug view of the model to better understand what
is going on.
* The {@link api/ng.directive:input input directives} simply refer
to the model and are data-bound.
* The inputs validate. (Try leaving them blank or entering non digits in the zip field)
* In your application you can simply read from or write to the model and the form will be updated.
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
reflected in the view.
-39
View File
@@ -1,39 +0,0 @@
@ngdoc overview
@name Cookbook: Hello World
@description
<doc:example>
<doc:source>
<script>
function HelloCntl($scope) {
$scope.name = 'World';
}
</script>
<div ng-controller="HelloCntl">
Your name: <input type="text" ng-model="name"/>
<hr/>
Hello {{name || "World"}}!
</div>
</doc:source>
<doc:scenario>
it('should change the binding when user enters text', function() {
expect(binding('name')).toEqual('World');
input('name').enter('angular');
expect(binding('name')).toEqual('angular');
});
</doc:scenario>
</doc:example>
# Things to notice
Take a look through the source and note:
* The script tag that {@link guide/bootstrap bootstraps} the Angular environment.
* The text {@link api/ng.directive:input input form control} which is
bound to the greeting name text.
* There is no need for listener registration and event firing on change events.
* The implicit presence of the `name` variable which is in the root {@link api/ng.$rootScope.Scope scope}.
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
* The concept of {@link guide/databinding data binding}, which reflects any
changes to the
input field in the greeting text.
-58
View File
@@ -1,58 +0,0 @@
@ngdoc overview
@name Cookbook
@description
Welcome to the Angular cookbook. Here we will show you typical uses of Angular by example.
# Hello World
{@link helloworld Hello World}: The simplest possible application that demonstrates the
classic Hello World!
# Basic Form
{@link form Basic Form}: Displaying forms to the user for editing is the bread and butter
of web applications. Angular makes forms easy through bidirectional data binding.
# Advanced Form
{@link advancedform Advanced Form}: Taking the form example to the next level and
providing advanced features such as dirty detection, form reverting and submit disabling if
validation errors exist.
# Model View Controller
{@link mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
to separate the behavior (JavaScript controller) from the presentation (HTML view). This
separation aids in maintainability and testability of your project.
# Multi-page App and Deep Linking
{@link deeplinking Deep Linking}: An AJAX application never navigates away from the
first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
prevent you from emailing links to a particular screen within your application.
Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
allowing you to send links to specific screens in your app.
# Services
{@link api/ng Services}: Services are long lived objects in your applications that are
available across controllers. A collection of useful services are pre-bundled with Angular but you
will likely add your own. Services are initialized using dependency injection, which resolves the
order of initialization. This safeguards you from the perils of global state (a common way to
implement long lived objects).
# External Resources
{@link buzz Resources}: Web applications must be able to communicate with the external
services to get and update data. Resources are the abstractions of external URLs which are
specially tailored to Angular data binding.
-128
View File
@@ -1,128 +0,0 @@
@ngdoc overview
@name Cookbook: MVC
@description
MVC allows for a clean and testable separation between the behavior (controller) and the view
(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
view. This makes it very easy for the controller and the view to share the model.
The model is a set of objects and primitives that are referenced from the Scope ($scope) object.
This makes it very easy to test the controller in isolation since one can simply instantiate the
controller and test without a view, because there is no connection between the controller and the
view.
<doc:example>
<doc:source>
<script>
function TicTacToeCntl($scope, $location) {
$scope.cellStyle= {
'height': '20px',
'width': '20px',
'border': '1px solid black',
'text-align': 'center',
'vertical-align': 'middle',
'cursor': 'pointer'
};
$scope.reset = function() {
$scope.board = [
['', '', ''],
['', '', ''],
['', '', '']
];
$scope.nextMove = 'X';
$scope.winner = '';
setUrl();
};
$scope.dropPiece = function(row, col) {
if (!$scope.winner && !$scope.board[row][col]) {
$scope.board[row][col] = $scope.nextMove;
$scope.nextMove = $scope.nextMove == 'X' ? 'O' : 'X';
setUrl();
}
};
$scope.reset();
$scope.$watch(function() { return $location.search().board;}, readUrl);
function setUrl() {
var rows = [];
angular.forEach($scope.board, function(row) {
rows.push(row.join(','));
});
$location.search({board: rows.join(';') + '/' + $scope.nextMove});
}
function grade() {
var b = $scope.board;
$scope.winner =
row(0) || row(1) || row(2) ||
col(0) || col(1) || col(2) ||
diagonal(-1) || diagonal(1);
function row(row) { return same(b[row][0], b[row][1], b[row][2]);}
function col(col) { return same(b[0][col], b[1][col], b[2][col]);}
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
function same(a, b, c) { return (a==b && b==c) ? a : '';};
}
function readUrl(value) {
if (value) {
value = value.split('/');
$scope.nextMove = value[1];
angular.forEach(value[0].split(';'), function(row, col){
$scope.board[col] = row.split(',');
});
grade();
}
}
}
</script>
<h3>Tic-Tac-Toe</h3>
<div ng-controller="TicTacToeCntl">
Next Player: {{nextMove}}
<div class="winner" ng-show="winner">Player {{winner}} has won!</div>
<table class="board">
<tr ng-repeat="row in board track by $index" style="height:15px;">
<td ng-repeat="cell in row track by $index" ng-style="cellStyle"
ng-click="dropPiece($parent.$index, $index)">{{cell}}</td>
</tr>
</table>
<button ng-click="reset()">reset board</button>
</div>
</doc:source>
<doc:scenario>
it('should play a game', function() {
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
expect(binding('nextMove')).toEqual('X');
piece(1, 2);
piece(3, 2);
piece(1, 3);
expect(element('.winner').text()).toEqual('Player X has won!');
});
function piece(row, col) {
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
}
</doc:scenario>
</doc:example>
# Things to notice
* The controller is defined in JavaScript and has no reference to the rendering logic.
* The controller is instantiated by Angular and injected into the view.
* The controller can be instantiated in isolation (without a view) and the code will still execute.
This makes it very testable.
* The HTML view is a projection of the model. In the above example, the model is stored in the
board variable.
* All of the controller's properties (such as board and nextMove) are available to the view.
* Changing the model changes the view.
* The view can call any controller function.
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
hash so the browser's back button will undo game steps. See deep-linking. This example calls {@link
api/ng.$rootScope.Scope#methods_$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
+1 -1
View File
@@ -11,7 +11,7 @@ For these reasons binding to event handler attributes (all attributes that start
An example code that would allow XSS vulnerability by evaluating user input in the window context could look like this:
```
<input ng-mode="username">
<input ng-model="username">
<div onclick="{{username}}">click me</div>
```
@@ -0,0 +1,10 @@
@ngdoc error
@name $httpBackend:noxhr
@fullName Unsupported XHR
@description
This error occurs in browsers that do not support XmlHttpRequest. AngularJS
supports Safari, Chrome, Firefox, Opera, IE8 and higher, and mobile browsers
(Android, Chrome Mobile, iOS Safari). To avoid this error, use an officially
supported browser.
+1 -1
View File
@@ -258,7 +258,7 @@ The table below explains in detail which animation events are triggered
| {@link api/ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
| {@link api/ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
| {@link api/ng.directive:ngIf#usage_animations ngIf} | enter and leave |
| {@link api/ng.directive:ngShow#usage_animations ngClass or &#123;&#123;class&#125;&#125;} | add and remove |
| {@link api/ng.directive:ngClass#usage_animations ngClass or &#123;&#123;class&#125;&#125;} | add and remove |
| {@link api/ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
For a full breakdown of the steps involved during each animation event, refer to the {@link api/ngAnimate.$animate API docs}.
+26 -24
View File
@@ -54,18 +54,18 @@ Try out the Live Preview above, and then let's walk through the example and desc
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding1.png">
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
processes this new markup from the template using the so called <a name="compiler">"{@link compiler compiler}"</a>.
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
The first kind of new markup are the so called <a name="directive">"{@link directive directives}"</a>.
They apply special behavior to attributes or elements in the HTML. In the example above we use the
They apply special behavior to attributes or elements in the HTML. In the example above we use the
{@link api/ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
initializes our application. Angular also defines a directive for the {@link api/ng.directive:input `input`}
element that adds extra behavior to the element. E.g. it is able to automatically validate that the entered
text is non empty by evaluating the `required` attribute.
element that adds extra behavior to the element. E.g. it is able to automatically validate that the entered
text is non empty by evaluating the `required` attribute.
The {@link api/ng.directive:ngModel `ng-model`} directive stores/updates
the value of the input field into/from a variable and shows the validation state of the input field by
the value of the input field into/from a variable and shows the validation state of the input field by
adding css classes. In the example we use these css classes to mark an empty input field with a red border.
<div class="alert alert-info">
@@ -120,7 +120,7 @@ different currencies and also pay the invoice.
return this.convertCurrency(this.qty * this.cost, this.inCurr, outCurr);
};
this.convertCurrency = function convertCurrency(amount, inCurr, outCurr) {
return amount * this.usdToForeignRates[outCurr] * 1 / this.usdToForeignRates[inCurr];
return amount * this.usdToForeignRates[outCurr] / this.usdToForeignRates[inCurr];
};
this.pay = function pay() {
window.alert("Thanks!");
@@ -195,20 +195,20 @@ Let's refactor our example and move the currency conversion into a service in an
<file name="finance2.js">
angular.module('finance2', [])
.factory('currencyConverter', function() {
var currencies = ['USD', 'EUR', 'CNY'],
usdToForeignRates = {
var currencies = ['USD', 'EUR', 'CNY'];
var usdToForeignRates = {
USD: 1,
EUR: 0.74,
CNY: 6.09
};
var convert = function (amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr];
}
return {
currencies: currencies,
convert: convert
};
function convert(amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
}
});
</file>
<file name="invoice2.js">
@@ -325,21 +325,15 @@ The following example shows how this is done with Angular:
var YAHOO_FINANCE_URL_PATTERN =
'http://query.yahooapis.com/v1/public/yql?q=select * from '+
'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK',
currencies = ['USD', 'EUR', 'CNY'],
usdToForeignRates = {};
refresh();
return {
currencies: currencies,
convert: convert,
refresh: refresh
};
'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK';
var currencies = ['USD', 'EUR', 'CNY'];
var usdToForeignRates = {};
function convert(amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
var convert = function (amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr];
}
function refresh() {
var refresh = function() {
var url = YAHOO_FINANCE_URL_PATTERN.
replace('PAIRS', 'USD' + currencies.join('","USD'));
return $http.jsonp(url).success(function(data) {
@@ -351,6 +345,14 @@ The following example shows how this is done with Angular:
usdToForeignRates = newUsdToForeignRates;
});
}
refresh();
return {
currencies: currencies,
convert: convert,
refresh: refresh
};
}]);
</file>
<file name="index.html">
@@ -3,9 +3,10 @@
@name Developer Guide: E2E Testing
@description
**If you're starting a new Angular project, you may want to look into
using {@link https://github.com/angular/protractor Protractor}, as it is going to
replace the current method of E2E Testing in the near future.**
**Angular Scenario Runner is in maintenance mode - If you're starting a new Angular project,
consider using {@link https://github.com/angular/protractor Protractor}.**
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
verify the correctness of new features, catch bugs and notice regressions.
@@ -160,7 +160,7 @@ encoded.
`$location` service has two configuration modes which control the format of the URL in the browser
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
HTML5 {@link http://www.w3.org/TR/html5/history.html History API}. Applications use the same API in
HTML5 {@link http://www.w3.org/TR/html5/browsers.html#history History API}. Applications use the same API in
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
facilitate the browser URL change and history management.
@@ -53,18 +53,19 @@ function myController(scope, notifyService) {
myController.$inject = ['$scope','notify'];
</script>
<div ng-controller="myController">
<div id="simple" ng-controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click 3 times to see an alert)</p>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should test service', function() {
expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
expect(element(by.id('simple')).element(by.model('message')).getAttribute('value'))
.toEqual('test');
});
</doc:scenario>
</doc:protractor>
</doc:example>
## Implicit Dependency Injection
@@ -95,7 +96,7 @@ function myController($scope, notify) {
};
}
</script>
<div ng-controller="myController">
<div id="implicit" ng-controller="myController">
<p>Let's try the notify service, that is implicitly injected into the controller...</p>
<input ng-init="message='test'" ng-model="message">
<button ng-click="callNotify(message);">NOTIFY</button>
@@ -13,7 +13,7 @@ Angular sets these CSS classes. It is up to your application to provide useful s
* `ng-binding`
- **Usage:** angular applies this class to any element that is attached to a data binding, via `ng-bind` or
{{}} curly braces, for example. (see {@link guide/databinding databinding} guide)
`{{}}` curly braces, for example. (see {@link guide/databinding databinding} guide)
* `ng-invalid`, `ng-valid`
- **Usage:** angular applies this class to an input widget element if that element's input does
+3 -3
View File
@@ -84,10 +84,10 @@ Here are some equivalent examples of elements that match `ngBind`:
<span x-ng-bind="name"></span> <br/>
</div>
</file>
<file name="scenario.js">
<file name="protractorTest.js">
it('should show off bindings', function() {
expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text())
.toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 October 4, 1947)');
expect(element(by.css('div[ng-controller="Ctrl1"] span[ng-bind]')).getText())
.toBe('Max Karl Ernst Ludwig Planck (April 23, 1858 October 4, 1947)');
});
</file>
</example>
+25 -25
View File
@@ -37,11 +37,11 @@ JavaScript, use the {@link api/ng.$rootScope.Scope#methods_$eval `$eval()`} meth
<doc:source>
1+2={{1+2}}
</doc:source>
<doc:scenario>
<doc:protractor>
it('should calculate expression in binding', function() {
expect(binding('1+2')).toEqual('3');
expect(element(by.binding('1+2')).getText()).toEqual('1+2=3');
});
</doc:scenario>
</doc:protractor>
</doc:example>
You can try evaluating different expressions here:
@@ -73,14 +73,14 @@ You can try evaluating different expressions here:
</ul>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should allow user expression testing', function() {
element('.expressions :button').click();
var li = using('.expressions ul').repeater('li');
expect(li.count()).toBe(1);
expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
element(by.css('.expressions button')).click();
var lis = element(by.css('.expressions ul')).element.all(by.repeater('expr in exprs'));
expect(lis.count()).toBe(1);
expect(lis.get(0).getText()).toEqual('[ X ] 3*10|currency => $30.00');
});
</doc:scenario>
</doc:protractor>
</doc:example>
@@ -99,7 +99,7 @@ prevent accidental access to the global state (a common source of subtle bugs).
$scope.name = 'World';
$scope.greet = function() {
($window.mockWindow || $window).alert('Hello ' + $scope.name);
$window.alert('Hello ' + $scope.name);
}
}
</script>
@@ -108,21 +108,21 @@ prevent accidental access to the global state (a common source of subtle bugs).
<button ng-click="greet()">Greet</button>
</div>
</doc:source>
<doc:scenario>
it('should calculate expression in binding', function() {
var alertText;
this.addFutureAction('set mock', function($window, $document, done) {
$window.mockWindow = {
alert: function(text){ alertText = text; }
};
done();
});
element(':button:contains(Greet)').click();
expect(this.addFuture('alert text', function(done) {
done(null, alertText);
})).toBe('Hello World');
});
</doc:scenario>
<doc:protractor>
it('should calculate expression in binding', function() {
if (browser.params.browser = 'safari') {
// Safari can't handle dialogs.
return;
};
element(by.css('[ng-click="greet()"]')).click();
var alertDialog = browser.switchTo().alert();
expect(alertDialog.getText()).toEqual('Hello World');
alertDialog.accept();
});
</doc:protractor>
</doc:example>
## Forgiving
+3 -1
View File
@@ -13,6 +13,7 @@ Everything you need to know about AngularJS
* {@link tutorial/index Official AngularJS Tutorial}
* [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/)
* [10 Reasons Why Developers Should Learn AngularJS](http://wintellect.com/blogs/jlikness/10-reasons-web-developers-should-learn-angularjs)
* [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c)
* [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM)
* [For folks with jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background)
@@ -87,7 +88,7 @@ This is a short list of libraries with specific support and documentation for wo
* **FireBase:** [AngularFire](http://angularfire.com/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/resources/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
@@ -113,6 +114,7 @@ This is a short list of libraries with specific support and documentation for wo
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html)
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
* **Paid onsite:**
[angularbootcamp.com](http://angularbootcamp.com/)
+1 -1
View File
@@ -333,7 +333,7 @@ information.
Dirty checking the scope for property changes is a common operation in Angular and for this reason
the dirty checking function must be efficient. Care should be taken that the dirty checking
function does not do any DOM access, as DOM access is orders of magnitude slower then property
function does not do any DOM access, as DOM access is orders of magnitude slower than property
access on JavaScript object.
## Integration with the browser event loop
+2 -2
View File
@@ -178,11 +178,11 @@ grunt --help
## Running the End-to-end Test Suite
Simply run:
Angular's end to end tests are run with Protractor. Simply run:
```shell
grunt test:e2e
```
This will start the webserver and run the tests.
This will start the webserver and run the tests on Chrome.
+1 -1
View File
@@ -100,7 +100,7 @@ Watch the July 17, 2012 talk
### How is Angular licensed?
The MIT License.
The {@link https://github.com/angular/angular.js/blob/master/LICENSE MIT License}.
### Can I download and use the Angular logo artwork?
+1 -1
View File
@@ -7,7 +7,7 @@ becoming an Angular expert.
1. Read the {@link guide/concepts conceptual overview}.<br/>Understand Angular's vocabulary and how all the Angular
components work together.
1. Do the {@link tutorial/ AngularJS Tutorial}.<br/>Walk end-to-end through building and application complete with tests
1. Do the {@link tutorial/ AngularJS Tutorial}.<br/>Walk end-to-end through building an application complete with tests
on top of a node.js web server. Covers every major AngularJS feature and show you how to set up your development
environment.
1. Download or clone the {@link https://github.com/angular/angular-seed Seed App project template}.<br/>Gives you a
+1 -1
View File
@@ -148,7 +148,7 @@ describe('PhoneCat controllers', function() {
});
});
beforeEach(module('phonecatApp'));
beforeEach(module('phonecatServices'));
-2
View File
@@ -8,8 +8,6 @@ previous steps using the `git checkout` command.
For more details and examples of the Angular concepts we touched on in this tutorial, see the
{@link guide/ Developer Guide}.
For several more examples of code, see the {@link cookbook/ Cookbook}.
When you are ready to start developing a project using Angular, we recommend that you bootstrap
your development with the {@link https://github.com/angular/angular-seed angular-seed} project.
+4 -1
View File
@@ -57,7 +57,10 @@ writer.makeDir('build/docs/', true).then(function() {
fileFutures.push(writer.output('partials/' + doc.section + '/' + id + '.html', doc.html()));
// If it has a sample Protractor test, output that as well.
if (doc.protractorTests.length) {
fileFutures.push(writer.output('ptore2e/' + doc.section + '/' + id + '_test.js', ngdoc.writeProtractorTest(doc)));
fileFutures.push(writer.output('ptore2e/' + doc.section + '/' + id + '.jquery_test.js',
ngdoc.writeProtractorTest(doc, 'index-jq-nocache.html#!/')));
fileFutures.push(writer.output('ptore2e/' + doc.section + '/' + id + '.jqlite_test.js',
ngdoc.writeProtractorTest(doc, 'index-nocache.html#!/')));
}
});
+5 -5
View File
@@ -51,7 +51,7 @@ exports.ngVersions = function() {
});
//match the future version of AngularJS that is set in the package.json file
return expandVersions(sortVersionsNatrually(versions), exports.ngCurrentVersion().full);
return expandVersions(sortVersionsNaturally(versions), exports.ngCurrentVersion().full);
function expandVersions(versions, latestVersion) {
var RC_VERSION = /rc\d/;
@@ -87,7 +87,7 @@ exports.ngVersions = function() {
return expanded;
};
function sortVersionsNatrually(versions) {
function sortVersionsNaturally(versions) {
var versionMap = {},
NON_RC_RELEASE_NUMBER = 999;
for(var i = versions.length - 1; i >= 0; i--) {
@@ -1110,15 +1110,15 @@ function scenarios(docs){
}
}
function writeProtractorTest(doc){
function writeProtractorTest(doc, pathPrefix){
var lines = [];
lines.push('describe("' + doc.section + '/' + doc.id + '", function() {');
lines.push(' beforeEach(function() {');
lines.push(' browser.get("index-nocache.html#!/' + doc.section + '/' + doc.id + '");');
lines.push(' browser.get("' + pathPrefix + doc.section + '/' + doc.id + '");');
lines.push(' });');
lines.push('');
doc.protractorTests.forEach(function(test){
lines.push(indentCode(trim(test), 2));
lines.push(indentCode(trim(test), 0));
lines.push('');
});
lines.push('});');
+1 -1
View File
@@ -16,4 +16,4 @@ RewriteCond %{HTTP_HOST} ^docs-next\.angularjs\.org$
RewriteRule appcache.manifest http://code.angularjs.org/next/docs/appcache.manifest [R=301]
## HTML5 URL Support ##
RewriteRule ^(guide|api|cookbook|misc|tutorial)(/.*)?$ index.html
RewriteRule ^(guide|api|misc|tutorial)(/.*)?$ index.html
+1 -5
View File
@@ -26,7 +26,7 @@
}
var indexFile = (location.pathname.match(/\/(index[^\.]*\.html)/) || ['', ''])[1],
rUrl = /(#!\/|api|guide|misc|tutorial|cookbook|error|index[^\.]*\.html).*$/,
rUrl = /(#!\/|api|guide|misc|tutorial|error|index[^\.]*\.html).*$/,
baseUrl = location.href.replace(rUrl, indexFile),
jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
debug = /index[^\.]*-debug\.html$/.test(baseUrl),
@@ -334,10 +334,6 @@
<div id="loading" ng-show="loading">Loading...</div>
<div ng-hide="loading" ng-include src="currentPage.partialUrl" onload="afterPartialLoaded()" autoscroll class="content slide-reveal"></div>
<div class="alert alert-info">
<a href="http://blog.angularjs.org/2013/11/farewell-disqus.html">Where did Disqus go?</a>
</div>
</div>
</div>
</div>
+1 -6
View File
@@ -111,9 +111,6 @@ docsApp.serviceFactory.docsSearch = ['$rootScope','lunrSearch', 'NG_PAGES',
angular.forEach(index.search(q), function(result) {
var item = NG_PAGES[result.ref];
var section = item.section;
if(section == 'cookbook') {
section = 'tutorial';
}
results[section] = results[section] || [];
if(results[section].length < 15) {
results[section].push(item);
@@ -630,7 +627,6 @@ docsApp.serviceFactory.sections = ['NG_PAGES', function sections(NG_PAGES) {
api: [],
tutorial: [],
misc: [],
cookbook: [],
error: [],
getPage: function(sectionId, partialId) {
var pages = sections[sectionId];
@@ -675,7 +671,7 @@ docsApp.controller.DocsController = function($scope, $rootScope, $location, $win
}
};
var OFFLINE_COOKIE_NAME = 'ng-offline',
DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)|(error)/,
DOCS_PATH = /^\/(api)|(guide)|(misc)|(tutorial)|(error)/,
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/,
GLOBALS = /^angular\.([^\.]+)$/,
ERROR = /^([a-zA-Z0-9_$]+:)?([a-zA-Z0-9_$]+)$/,
@@ -737,7 +733,6 @@ docsApp.controller.DocsController = function($scope, $rootScope, $location, $win
guide: 'Developer Guide',
misc: 'Miscellaneous',
tutorial: 'Tutorial',
cookbook: 'Examples',
error: 'Error Reference'
};
+1 -2
View File
@@ -1,11 +1,10 @@
# i18n directory overview:
- closure/ - closure files we use for ruleset generation
- locale/ - angular's locale ruleset files
- src/ - source files
- spec/ - spec files for stuff in src directory
- generate.sh - runs src scripts on closure dir and stores output in locale dir
- update-closure.sh - downloads the latest version of closure files from public svn repo
- update-closure.sh - downloads the latest version of closure files from public git repo
The closure files (maintained by Shanjian Li (shanjian)) change very rarely, so we don't need to
regenerate locale files very often.
+6 -1
View File
@@ -1,7 +1,12 @@
#!/bin/bash
set -e
BASE_DIR=`dirname $0`
cd $BASE_DIR
./run-tests.sh
node src/closureSlurper.js
../node_modules/.bin/jasmine-node spec/ --noColor && node src/closureSlurper.js
+2 -1
View File
@@ -2,4 +2,5 @@
set -e
PARENT_DIR="$(dirname "$0")"
jasmine-node "$PARENT_DIR"/spec/
../node_modules/.bin/jasmine-node "$PARENT_DIR"/spec/
+28 -12
View File
@@ -2,7 +2,7 @@
'use strict';
var Q = require('q'),
qfs = require('q-fs'),
qfs = require('q-io/fs'),
converter = require('./converter.js'),
util = require('./util.js'),
closureI18nExtractor = require('./closureI18nExtractor.js'),
@@ -47,24 +47,40 @@ function extractPlurals() {
function writeLocaleFiles() {
console.log('Final stage: Writing angular locale files to directory: %j', NG_LOCALE_DIR);
var writePromises = [];
var result = Q.defer();
var localeIds = Object.keys(localeInfo);
var num_files = 0;
localeIds.forEach(function(localeID) {
console.log('Generated %j locale files.', localeIds.length);
loop();
return result.promise;
// Need to use a loop and not write the files in parallel,
// as otherwise we will get the error EMFILE, which means
// we have too many open files.
function loop() {
var nextPromise;
if (localeIds.length) {
nextPromise = process(localeIds.pop()) || Q.when();
nextPromise.then(loop, result.reject);
} else {
result.resolve(num_files);
}
}
function process(localeID) {
var content = closureI18nExtractor.outputLocale(localeInfo, localeID);
if (!content) return;
var correctedLocaleId = closureI18nExtractor.correctedLocaleId(localeID);
var filename = NG_LOCALE_DIR + 'angular-locale_' + correctedLocaleId + '.js'
writePromises.push(
qfs.write(filename, content)
.then(function () {
console.log('Wrote ' + filename);
++num_files;
}));
console.log('Writing ' + filename);
});
console.log('Generated %j locale files.', localeIds.length);
return Q.all(writePromises).then(function() { return num_files });
return qfs.write(filename, content)
.then(function () {
console.log('Wrote ' + filename);
++num_files;
});
}
}
/**
+6 -5
View File
@@ -7,8 +7,9 @@ cd $BASE_DIR
set -x # Trace commands as they're executed.
curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/currency.js > closure/currencySymbols.js
curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/datetimesymbols.js > closure/datetimeSymbols.js
curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/datetimesymbolsext.js > closure/datetimeSymbolsExt.js
curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/numberformatsymbols.js > closure/numberSymbols.js
curl http://closure-library.googlecode.com/svn/trunk/closure/goog/i18n/pluralrules.js > closure/pluralRules.js
# use the github repo as it is more up to date than the svn repo
curl https://closure-library.googlecode.com/git/closure/goog/i18n/currency.js > closure/currencySymbols.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/datetimesymbols.js > closure/datetimeSymbols.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/datetimesymbolsext.js > closure/datetimeSymbolsExt.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/numberformatsymbols.js > closure/numberSymbols.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/pluralrules.js > closure/pluralRules.js
Binary file not shown.
+3 -8
View File
@@ -13,12 +13,6 @@ then
BROWSERS="Chrome,Firefox,Opera,/Users/jenkins/bin/safari.sh,/Users/jenkins/bin/ie8.sh,/Users/jenkins/bin/ie9.sh"
fi
if [[ -z "$BROWSERS_E2E" ]]
then
BROWSERS_E2E="Chrome,Firefox,/Users/jenkins/bin/safari.sh"
fi
# CLEAN #
rm -f angular.min.js.gzip.size
rm -f angular.js.size
@@ -28,6 +22,8 @@ rm -f angular.js.size
npm install --color false
grunt ci-checks package --no-color
mkdir -p test_out
# DOCS generator unit tests #
grunt test:docgen --no-color
@@ -35,8 +31,7 @@ grunt test:docgen --no-color
grunt test:unit --browsers $BROWSERS --reporters=dots,junit --no-colors --no-color
# END TO END TESTS #
grunt test:e2e --browsers $BROWSERS_E2E --reporters=dots,junit --no-colors --no-color
grunt test:protractor
grunt test:ci-protractor
# Promises/A+ TESTS #
grunt test:promises-aplus --no-color
-27
View File
@@ -1,27 +0,0 @@
var sharedConfig = require('./karma-shared.conf');
module.exports = function(config) {
sharedConfig(config, {testName: 'AngularJS: e2e', logFile: 'karma-e2e.log'});
config.set({
frameworks: [],
files: [
'build/angular-scenario.js',
'node_modules/karma-ng-scenario/lib/adapter.js',
'build/docs/docs-scenario.js'
],
proxies: {
// angular.js, angular-resource.js, etc
'/angular': 'http://localhost:8000/build/angular',
'/': 'http://localhost:8000/build/docs/'
},
junitReporter: {
outputFile: 'test_out/e2e.xml',
suite: 'E2E'
},
browserNoActivityTimeout: 90000
});
};
+1 -1
View File
@@ -65,7 +65,7 @@ module.exports = function(grunt) {
util.updateWebdriver.call(util, this.async());
});
grunt.registerMultiTask('runprotractor', 'Run Protractor integration tests', function() {
grunt.registerMultiTask('protractor', 'Run Protractor integration tests', function() {
util.startProtractor.call(util, this.data, this.async());
});
+81 -18
View File
@@ -2,6 +2,7 @@ var fs = require('fs');
var shell = require('shelljs');
var grunt = require('grunt');
var spawn = require('child_process').spawn;
var semver = require('semver');
var version;
var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP mode. */\n\n';
@@ -32,31 +33,82 @@ module.exports = {
getVersion: function(){
if (version) return version;
var package = JSON.parse(fs.readFileSync('package.json', 'UTF-8'));
var match = package.version.match(/^([^\-]*)(?:\-(.+))?$/);
var semver = match[1].split('.');
try {
var fullVersion = match[1];
var gitTag = getTagOfCurrentCommit();
var semVerVersion, codeName, fullVersion;
if (gitTag) {
// tagged release
fullVersion = semVerVersion = semver.valid(gitTag);
codeName = getTaggedReleaseCodeName(gitTag);
} else {
// snapshot release
semVerVersion = getSnapshotVersion();
fullVersion = semVerVersion + '-' + getSnapshotSuffix();
codeName = 'snapshot'
}
if (match[2]) {
fullVersion += '-';
fullVersion += (match[2] == 'snapshot') ? getSnapshotSuffix() : match[2];
var versionParts = semVerVersion.match(/(\d+)\.(\d+)\.(\d+)/);
version = {
full: fullVersion,
major: versionParts[1],
minor: versionParts[2],
dot: versionParts[3],
codename: codeName,
cdn: package.cdnVersion
};
return version;
} catch (e) {
grunt.fail.warn(e);
}
version = {
full: fullVersion,
major: semver[0],
minor: semver[1],
dot: semver[2].replace(/rc\d+/, ''),
codename: package.codename,
cdn: package.cdnVersion
};
function getTagOfCurrentCommit() {
var gitTagResult = shell.exec('git describe --exact-match', {silent:true});
var gitTagOutput = gitTagResult.output.trim();
var branchVersionPattern = new RegExp(package.branchVersion.replace('.', '\\.').replace('*', '\\d+'));
if (gitTagResult.code === 0 && gitTagOutput.match(branchVersionPattern)) {
return gitTagOutput;
} else {
return null;
}
}
return version;
function getTaggedReleaseCodeName(tagName) {
var tagMessage = shell.exec('git cat-file -p '+ tagName +' | grep "codename"', {silent:true}).output;
var codeName = tagMessage && tagMessage.match(/codename\((.*)\)/)[1];
if (!codeName) {
throw new Error("Could not extract release code name. The message of tag "+tagName+
" must match '*codename(some release name)*'");
}
return codeName;
}
function getSnapshotVersion() {
var oldTags = shell.exec('git tag -l v'+package.branchVersion, {silent:true}).output.trim().split('\n');
// ignore non semver versions.
oldTags = oldTags.filter(function(version) {
return version && semver.valid(version);
});
if (oldTags.length) {
oldTags.sort(semver.compare);
semVerVersion = oldTags[oldTags.length-1];
if (semVerVersion.indexOf('-') !== -1) {
semVerVersion = semver.inc(semVerVersion, 'prerelease');
} else {
semVerVersion = semver.inc(semVerVersion, 'patch');
}
} else {
semVerVersion = semver.valid(package.branchVersion.replace(/\*/g, '0'));
}
return semVerVersion;
}
function getSnapshotSuffix() {
var jenkinsBuild = process.env.BUILD_NUMBER || 'local';
var jenkinsBuild = process.env.TRAVIS_BUILD_NUMBER || process.env.BUILD_NUMBER || 'local';
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
return 'build.'+jenkinsBuild+'+sha.'+hash;
}
@@ -84,7 +136,13 @@ module.exports = {
},
updateWebdriver: function(done){
updateWebdriver: function(done){
if (process.env.TRAVIS) {
// Skip the webdriver-manager update on Travis, since the browsers will
// be provided remotely.
done();
return;
}
var p = spawn('node', ['node_modules/protractor/bin/webdriver-manager', 'update']);
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
@@ -99,11 +157,16 @@ module.exports = {
var sauceKey = grunt.option('sauceKey');
var tunnelIdentifier = grunt.option('capabilities.tunnel-identifier');
var sauceBuild = grunt.option('capabilities.build');
var browser = grunt.option('browser');
var args = ['node_modules/protractor/bin/protractor', config];
if (sauceUser) args.push('--sauceUser=' + sauceUser);
if (sauceKey) args.push('--sauceKey=' + sauceKey);
if (tunnelIdentifier) args.push('--capabilities.tunnel-identifier=' + tunnelIdentifier);
if (sauceBuild) args.push('--capabilities.build=' + sauceBuild);
if (browser) {
args.push('--browser=' + browser);
args.push('--params.browser=' + browser);
}
var p = spawn('node', args);
+19 -17
View File
@@ -1,22 +1,29 @@
{
"name": "angularjs",
"version": "1.2.10",
"cdnVersion": "1.2.9",
"codename": "augmented-serendipity",
"branchVersion": "1.2.*",
"cdnVersion": "1.2.12",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
},
"devDependencies": {
"grunt": "~0.4.1",
"bower": "~1.2.2",
"grunt": "~0.4.2",
"grunt-bump": "~0.0.13",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jshint": "~0.7.2",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs-checker": "~0.3.2",
"grunt-merge-conflict": "~0.0.1",
"grunt-parallel": "~0.3.1",
"grunt-shell": "~0.4.0",
"load-grunt-tasks": "~0.3.0",
"bower": "~1.2.2",
"jasmine-node": "~1.11.0",
"q": "~0.9.2",
"q": "~1.0.0",
"q-io": "~1.10.6",
"qq": "~0.3.5",
"shelljs": "~0.2.6",
@@ -29,19 +36,16 @@
"karma-sauce-launcher": "0.2.0",
"karma-script-launcher": "0.1.0",
"karma-browserstack-launcher": "0.0.7",
"protractor": "~0.16.1",
"protractor": "~0.18.0",
"yaml-js": "~0.0.8",
"marked": "0.2.9",
"rewire": "1.1.3",
"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",
"promises-aplus-tests": "~1.3.2",
"grunt-shell": "~0.4.0",
"semver": "~2.1.0",
"lodash": "~2.1.0",
"browserstacktunnel-wrapper": "~1.1.1"
"browserstacktunnel-wrapper": "~1.1.1",
"grunt-jscs-checker": "~0.3.2",
"jasmine-reporters": "~0.2.1"
},
"licenses": [
{
@@ -49,7 +53,5 @@
"url": "https://github.com/angular/angular.js/blob/master/LICENSE"
}
],
"dependencies": {
"grunt-contrib-jshint": "~0.7.2"
}
"dependencies": {}
}
+3 -2
View File
@@ -2,12 +2,13 @@ exports.config = {
allScriptsTimeout: 11000,
specs: [
'build/docs/ptore2e/**/*.js',
'build/docs/ptore2e/**/*jqlite_test.js',
'test/e2e/docsAppE2E.js'
],
capabilities: {
'browserName': 'chrome'
'browserName': 'chrome',
'name': 'Angular E2E: jqlite'
},
baseUrl: 'http://localhost:8000/build/docs/',
+36
View File
@@ -0,0 +1,36 @@
exports.config = {
allScriptsTimeout: 11000,
specs: [
'build/docs/ptore2e/**/*.js',
'test/e2e/docsAppE2E.js'
],
capabilities: {
'browserName': 'chrome'
},
baseUrl: 'http://localhost:8000/build/docs/',
framework: 'jasmine',
onPrepare: function() {
// Disable animations so e2e tests run more quickly
var disableNgAnimate = function() {
angular.module('disableNgAnimate', []).run(function($animate) {
$animate.enabled(false);
});
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
require('jasmine-reporters');
jasmine.getEnv().addReporter(
new jasmine.JUnitXmlReporter('test_out/e2e-' + this.capabilities.browserName + '-', true, true));
},
jasmineNodeOpts: {
defaultTimeoutInterval: 30000,
showColors: false
}
};
+32
View File
@@ -0,0 +1,32 @@
exports.config = {
allScriptsTimeout: 11000,
specs: [
'build/docs/ptore2e/**/*jquery_test.js',
'test/e2e/docsAppE2E.js'
],
capabilities: {
'browserName': 'chrome',
'name': 'Angular E2E: jquery'
},
baseUrl: 'http://localhost:8000/build/docs/',
framework: 'jasmine',
onPrepare: function() {
// Disable animations so e2e tests run more quickly
var disableNgAnimate = function() {
angular.module('disableNgAnimate', []).run(function($animate) {
$animate.enabled(false);
});
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
},
jasmineNodeOpts: {
defaultTimeoutInterval: 30000
}
};
-20
View File
@@ -1,20 +0,0 @@
#!/bin/bash
echo "############################################"
echo "## Remove "-snapshot" from version ########"
echo "############################################"
ARG_DEFS=()
function run {
cd ../..
replaceJsonProp "package.json" "version" "(.*)-snapshot" "\2"
VERSION=$(readJsonProp "package.json" "version")
git add package.json
git commit -m "chore(release): cut v$VERSION release"
git tag -m "v$VERSION" v$VERSION
}
source $(dirname $0)/../utils.inc
@@ -1,24 +0,0 @@
#!/bin/bash
echo "############################################"
echo "## Increment version, add "-snapshot" and set version name ##"
echo "############################################"
ARG_DEFS=(
"--next-version-type=(patch|minor|major)"
"--next-version-name=(.+)"
)
function run {
cd ../..
grunt bump:$NEXT_VERSION_TYPE
NEXT_VERSION=$(readJsonProp "package.json" "version")
replaceJsonProp "package.json" "version" "(.*)" "\2-snapshot"
replaceJsonProp "package.json" "codename" ".*" "$NEXT_VERSION_NAME"
git add package.json
git commit -m "chore(release): start v$NEXT_VERSION ($NEXT_VERSION)"
}
source $(dirname $0)/../utils.inc
-42
View File
@@ -1,42 +0,0 @@
#!/bin/bash
# Script for updating angular.js repo from current local build.
echo "#################################"
echo "## Update angular.js ###"
echo "#################################"
ARG_DEFS=(
"--action=(prepare|publish)"
"--next-version-type=(patch|minor|major)"
"--next-version-name=(.+)"
"[--no-test=(true|false)]"
)
function init {
cd ../..
}
function prepare() {
./scripts/angular.js/finalize-version.sh
# Build
if [[ $NO_TEST == "true" ]]; then
npm install --color false
grunt ci-checks package --no-color
else
./jenkins_build.sh
fi
./scripts/angular.js/initialize-new-version.sh --next-version-type=$NEXT_VERSION_TYPE --next-version-name=$NEXT_VERSION_NAME
}
function publish() {
BRANCH=$(git rev-parse --abbrev-ref HEAD)
# push the commits to github
git push origin $BRANCH
# push the release tag
git push origin v`cat build/version.txt`
}
source $(dirname $0)/../utils.inc
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
# Tags a release
# so that travis can do the actual release.
echo "#################################"
echo "## Tag angular.js for a release #"
echo "#################################"
ARG_DEFS=(
"--action=(prepare|publish)"
"--commit-sha=(.*)"
# the version number of the release.
# e.g. 1.2.12 or 1.2.12-rc.1
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
"--version-name=(.+)"
)
function checkVersionNumber() {
BRANCH_PATTERN=$(readJsonProp "package.json" "branchVersion")
if [[ $VERSION_NUMBER != $BRANCH_PATTERN ]]; then
echo "version-number needs to match $BRANCH_PATTERN on this branch"
usage
fi
}
function init {
cd ../..
checkVersionNumber
TAG_NAME="v$VERSION_NUMBER"
}
function prepare() {
git tag "$TAG_NAME" -m "chore(release): $TAG_NAME codename($VERSION_NAME)" "$COMMIT_SHA"
}
function publish() {
# push the tag to github
git push origin $TAG_NAME
}
source $(dirname $0)/../utils.inc
+38 -13
View File
@@ -1,38 +1,63 @@
#!/bin/bash
# tags the current commit as a release and publishes all artifacts to
# the different repositories.
# Note: This will also works if the commit is in the past!
echo "#################################"
echo "#### Cut release ################"
echo "#### cut release ############"
echo "#################################"
ARG_DEFS=(
"--next-version-type=(patch|minor|major)"
"--next-version-name=(.+)"
# require the git dryrun flag so the script can't be run without
# thinking about this!
"--git-push-dryrun=(true|false)"
"[--no-test=(true|false)]"
# The sha to release. Needs to be the same as HEAD.
# given as parameter to double check.
"--commit-sha=(.*)"
# the version number of the release.
# e.g. 1.2.12 or 1.2.12-rc.1
"--version-number=([0-9]+\.[0-9]+\.[0-9]+(-[a-z]+\.[0-9]+)?)"
# the codename of the release
"--version-name=(.+)"
)
function init {
NG_ARGS=("$@")
if [[ $(git rev-parse --short HEAD) != $COMMIT_SHA ]]; then
echo "HEAD is not at $COMMIT_SHA"
usage
fi
if [[ ! $VERBOSE ]]; then
VERBOSE=false
fi
if [[ ! $NO_TEST ]]; then
NO_TEST=false
fi
VERBOSE_ARG="--verbose=$VERBOSE"
NO_TEST_ARG="--no_test=$NO_TEST"
}
function build {
cd ../..
npm install --color false
grunt ci-checks package --no-color
cd $SCRIPT_DIR
}
function phase {
ACTION_ARG="--action=$1"
../angular.js/publish.sh $ACTION_ARG $VERBOSE_ARG $NO_TEST_ARG \
--next-version-type=$NEXT_VERSION_TYPE --next-version-name=$NEXT_VERSION_NAME
../angular.js/tag-release.sh $ACTION_ARG $VERBOSE_ARG\
--version-number=$VERSION_NUMBER --version-name=$VERSION_NAME\
--commit-sha=$COMMIT_SHA
if [[ $1 == "prepare" ]]; then
# The build requires the tag to be set already!
build
fi
../code.angularjs.org/publish.sh $ACTION_ARG $VERBOSE_ARG
../bower/publish.sh $ACTION_ARG $VERBOSE_ARG
../angular-seed/publish.sh $ACTION_ARG $VERBOSE_ARG $NO_TEST_ARG
../angular-phonecat/publish.sh $ACTION_ARG $VERBOSE_ARG $NO_TEST_ARG
../angular-seed/publish.sh $ACTION_ARG $VERBOSE_ARG --no-test=true
../angular-phonecat/publish.sh $ACTION_ARG $VERBOSE_ARG --no-test=true
}
function run {
+8 -4
View File
@@ -10,11 +10,15 @@ if [ $JOB = "unit" ]; then
grunt test:promises-aplus
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
elif [ $JOB = "e2e" ]; then
grunt test:e2e --browsers SL_Chrome --reporters dots
grunt test:protractor --sauceUser $SAUCE_USERNAME \
export GRUNT_TARGET="test:protractor"
if [ $JQVERSION = "jquery" ]; then
GRUNT_TARGET="test:jq-protractor"
fi
grunt $GRUNT_TARGET --sauceUser $SAUCE_USERNAME \
--sauceKey $SAUCE_ACCESS_KEY \
--capabilities.tunnel-identifier=$TRAVIS_JOB_NUMBER \
--capabilities.build=$TRAVIS_BUILD_NUMBER
--capabilities.build=$TRAVIS_BUILD_NUMBER \
--browser=$BROWSER
else
echo "Unknown job type. Please set JOB=unit or JOB=e2e."
echo "Unknown job type. Please set JOB=unit or JOB=e2e-*."
fi
+6 -4
View File
@@ -81,6 +81,7 @@
-assertNotHasOwnProperty,
-getter,
-getBlockElements,
-hasOwnProperty,
*/
@@ -96,7 +97,7 @@
* @returns {string} Lowercased string.
*/
var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
var hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* @ngdoc function
@@ -192,7 +193,7 @@ function isArrayLike(obj) {
* is the value of an object property or an array element and `key` is the object property key or
* array element index. Specifying a `context` for the function is optional.
*
* It is worth nothing that `.forEach` does not iterate over inherited properties because it filters
* It is worth noting that `.forEach` does not iterate over inherited properties because it filters
* using the `hasOwnProperty` method.
*
<pre>
@@ -201,7 +202,7 @@ function isArrayLike(obj) {
angular.forEach(values, function(value, key){
this.push(key + ': ' + value);
}, log);
expect(log).toEqual(['name: misko', 'gender:male']);
expect(log).toEqual(['name: misko', 'gender: male']);
</pre>
*
* @param {Object|Array} obj Object to iterate over.
@@ -772,7 +773,7 @@ function shallowCopy(src, dst) {
for(var key in src) {
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
// so we don't need to worry about using our custom hasOwnProperty here
if (src.hasOwnProperty(key) && key.charAt(0) !== '$' && key.charAt(1) !== '$') {
if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
@@ -1131,6 +1132,7 @@ function encodeUriQuery(val, pctEncodeSpaces) {
<file name="index.html">
<div ng-controller="ngAppDemoController">
I can add: {{a}} + {{b}} = {{ a+b }}
</div>
</file>
<file name="script.js">
angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
+10 -11
View File
@@ -482,17 +482,16 @@ function annotate(fn) {
* Here is an example of registering a service using
* {@link AUTO.$provide#methods_service $provide.service(class)}.
* <pre>
* $provide.service('ping', ['$http', function($http) {
* var Ping = function() {
* this.$http = $http;
* };
* var Ping = function($http) {
* this.$http = $http;
* };
*
* Ping.$inject = ['$http'];
*
* Ping.prototype.send = function() {
* return this.$http.get('/ping');
* };
*
* return Ping;
* }]);
* Ping.prototype.send = function() {
* return this.$http.get('/ping');
* };
* $provide.service('ping', Ping);
* </pre>
* You would then inject and use this service like this:
* <pre>
@@ -590,7 +589,7 @@ function annotate(fn) {
* Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
* calls to {@link ng.$log#error $log.warn()}.
* <pre>
* $provider.decorator('$log', ['$delegate', function($delegate) {
* $provide.decorator('$log', ['$delegate', function($delegate) {
* $delegate.warn = $delegate.error;
* return $delegate;
* }]);
+13 -2
View File
@@ -40,7 +40,7 @@
* - [`after()`](http://api.jquery.com/after/)
* - [`append()`](http://api.jquery.com/append/)
* - [`attr()`](http://api.jquery.com/attr/)
* - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
* - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
@@ -67,7 +67,7 @@
* - [`text()`](http://api.jquery.com/text/)
* - [`toggleClass()`](http://api.jquery.com/toggleClass/)
* - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
* - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces
* - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
* - [`val()`](http://api.jquery.com/val/)
* - [`wrap()`](http://api.jquery.com/wrap/)
*
@@ -107,6 +107,14 @@ var jqCache = JQLite.cache = {},
? function(element, type, fn) {element.removeEventListener(type, fn, false); }
: function(element, type, fn) {element.detachEvent('on' + type, fn); });
/*
* !!! This is an undocumented "private" function !!!
*/
var jqData = JQLite._data = function(node) {
//jQuery always returns an object on cache miss
return this.cache[node[this.expando]] || {};
};
function jqNextId() { return ++jqId; }
@@ -175,6 +183,9 @@ function JQLite(element) {
if (element instanceof JQLite) {
return element;
}
if (isString(element)) {
element = trim(element);
}
if (!(this instanceof JQLite)) {
if (isString(element) && element.charAt(0) != '<') {
throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
+23
View File
@@ -222,6 +222,29 @@ var $AnimateProvider = ['$provide', function($provide) {
done && $timeout(done, 0, false);
},
/**
*
* @ngdoc function
* @name ng.$animate#setClass
* @methodOf ng.$animate
* @function
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
* @param {jQuery/jqLite element} element the element which will it's CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {function=} done the callback function (if provided) that will be fired after the
* CSS classes have been set on the element
*/
setClass : function(element, add, remove, done) {
forEach(element, function (element) {
jqLiteAddClass(element, add);
jqLiteRemoveClass(element, remove);
});
done && $timeout(done, 0, false);
},
enabled : noop
};
}];
+59 -16
View File
@@ -424,13 +424,17 @@
<div compile="html"></div>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should auto compile', function() {
expect(element('div[compile]').text()).toBe('Hello Angular');
input('html').enter('{{name}}!');
expect(element('div[compile]').text()).toBe('Angular!');
var textarea = $('textarea');
var output = $('div[compile]');
// The initial state reads 'Hello Angular'.
expect(output.getText()).toBe('Hello Angular');
textarea.clear();
textarea.sendKeys('{{name}}!');
expect(output.getText()).toBe('Angular!');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
@@ -498,7 +502,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|tbody)(\s+[^>]*)?>/i;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -685,8 +690,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* @param {string} oldClasses The former CSS className value
*/
$updateClass : function(newClasses, oldClasses) {
this.$removeClass(tokenDifference(oldClasses, newClasses));
this.$addClass(tokenDifference(newClasses, oldClasses));
var toAdd = tokenDifference(newClasses, oldClasses);
var toRemove = tokenDifference(oldClasses, newClasses);
if(toAdd.length === 0) {
$animate.removeClass(this.$$element, toRemove);
} else if(toRemove.length === 0) {
$animate.addClass(this.$$element, toAdd);
} else {
$animate.setClass(this.$$element, toAdd, toRemove);
}
},
/**
@@ -1138,7 +1151,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
templateDirective = previousCompileContext.templateDirective,
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
hasTranscludeDirective = false,
hasElementTranscludeDirective = false,
hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
$compileNode = templateAttrs.$$element = jqLite(compileNode),
directive,
directiveName,
@@ -1192,7 +1205,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
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,
// This option should only be used by directives that know how to safely handle element transclusion,
// where the transcluded nodes are added or replaced after linking.
if (!directive.$$tlb) {
assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode);
@@ -1239,9 +1252,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (directive.replace) {
replaceDirective = directive;
$template = jqLite('<div>' +
trim(directiveValue) +
'</div>').contents();
$template = directiveTemplateContents(directiveValue);
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -1312,6 +1323,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
return nodeLinkFn;
@@ -1639,6 +1651,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function directiveTemplateContents(template) {
var type;
template = trim(template);
if ((type = TABLE_CONTENT_REGEXP.exec(template))) {
type = type[1].toLowerCase();
var table = jqLite('<table>' + template + '</table>'),
tbody = table.children('tbody'),
leaf = /(td|th)/.test(type) && table.find('tr');
if (tbody.length && type !== 'tbody') {
table = tbody;
}
if (leaf && leaf.length) {
table = leaf;
}
return table.contents();
}
return jqLite('<div>' +
template +
'</div>').contents();
}
function compileTemplateUrl(directives, $compileNode, tAttrs,
$rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) {
var linkQueue = [],
@@ -1663,7 +1697,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
content = denormalizeTemplate(content);
if (origAsyncDirective.replace) {
$template = jqLite('<div>' + trim(content) + '</div>').contents();
$template = directiveTemplateContents(content);
compileNode = $template[0];
if ($template.length != 1 || compileNode.nodeType !== 1) {
@@ -1707,9 +1741,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
linkNode = $compileNode[0];
if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
// it was cloned therefore we have to clone as well.
linkNode = jqLiteClone(compileNode);
var oldClasses = beforeTemplateLinkNode.className;
if (!(previousCompileContext.hasElementTranscludeDirective &&
origAsyncDirective.replace)) {
// it was cloned therefore we have to clone as well.
linkNode = jqLiteClone(compileNode);
}
replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
// Copy in CSS classes from original node
safeAddClass(jqLite(linkNode), oldClasses);
}
if (afterTemplateNodeLinkFn.transclude) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
+55 -46
View File
@@ -41,46 +41,55 @@
<a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br />
<a id="link-6" ng-href="{{value}}">link</a> (link, change location)
</doc:source>
<doc:scenario>
<doc:protractor>
it('should execute ng-click but not reload when href without value', function() {
element('#link-1').click();
expect(input('value').val()).toEqual('1');
expect(element('#link-1').attr('href')).toBe("");
element(by.id('link-1')).click();
expect(element(by.model('value')).getAttribute('value')).toEqual('1');
expect(element(by.id('link-1')).getAttribute('href')).toBe('');
});
it('should execute ng-click but not reload when href empty string', function() {
element('#link-2').click();
expect(input('value').val()).toEqual('2');
expect(element('#link-2').attr('href')).toBe("");
element(by.id('link-2')).click();
expect(element(by.model('value')).getAttribute('value')).toEqual('2');
expect(element(by.id('link-2')).getAttribute('href')).toBe('');
});
it('should execute ng-click and change url when ng-href specified', function() {
expect(element('#link-3').attr('href')).toBe("/123");
expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/);
element('#link-3').click();
expect(browser().window().path()).toEqual('/123');
element(by.id('link-3')).click();
// At this point, we navigate away from an Angular page, so we need
// to use browser.driver to get the base webdriver.
browser.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
return url.match(/\/123$/);
});
}, 1000, 'page should navigate to /123');
});
it('should execute ng-click but not reload when href empty string and name specified', function() {
element('#link-4').click();
expect(input('value').val()).toEqual('4');
expect(element('#link-4').attr('href')).toBe('');
element(by.id('link-4')).click();
expect(element(by.model('value')).getAttribute('value')).toEqual('4');
expect(element(by.id('link-4')).getAttribute('href')).toBe('');
});
it('should execute ng-click but not reload when no href but name specified', function() {
element('#link-5').click();
expect(input('value').val()).toEqual('5');
expect(element('#link-5').attr('href')).toBe(undefined);
element(by.id('link-5')).click();
expect(element(by.model('value')).getAttribute('value')).toEqual('5');
expect(element(by.id('link-5')).getAttribute('href')).toBe(null);
});
it('should only change url when only ng-href', function() {
input('value').enter('6');
expect(element('#link-6').attr('href')).toBe('6');
element(by.model('value')).clear();
element(by.model('value')).sendKeys('6');
expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
element('#link-6').click();
expect(browser().location().url()).toEqual('/6');
element(by.id('link-6')).click();
expect(browser.getCurrentUrl()).toMatch(/\/6$/);
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
@@ -165,13 +174,13 @@
Click me to toggle: <input type="checkbox" ng-model="checked"><br/>
<button ng-model="button" ng-disabled="checked">Button</button>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should toggle button', function() {
expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
input('checked').check();
expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeFalsy();
element(by.model('checked')).click();
expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeTruthy();
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
* @element INPUT
@@ -200,13 +209,13 @@
Check me to check both: <input type="checkbox" ng-model="master"><br/>
<input id="checkSlave" type="checkbox" ng-checked="master">
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check both checkBoxes', function() {
expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
input('master').check();
expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
element(by.model('master')).click();
expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
* @element INPUT
@@ -235,13 +244,13 @@
Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/>
<input type="text" ng-readonly="checked" value="I'm Angular"/>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should toggle readonly attr', function() {
expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
input('checked').check();
expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeFalsy();
element(by.model('checked')).click();
expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeTruthy();
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
* @element INPUT
@@ -274,13 +283,13 @@
<option id="greet" ng-selected="selected">Greetings!</option>
</select>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should select Greetings!', function() {
expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
input('selected').check();
expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
element(by.model('selected')).click();
expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
* @element OPTION
@@ -310,13 +319,13 @@
<summary>Show/Hide me</summary>
</details>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should toggle open', function() {
expect(element('#details').prop('open')).toBeFalsy();
input('open').check();
expect(element('#details').prop('open')).toBeTruthy();
expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
element(by.model('open')).click();
expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
* @element DETAILS
+16 -7
View File
@@ -305,18 +305,27 @@ function FormController(element, attrs) {
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should initialize to model', function() {
expect(binding('userType')).toEqual('guest');
expect(binding('myForm.input.$valid')).toEqual('true');
var userType = element(by.binding('userType'));
var valid = element(by.binding('myForm.input.$valid'));
expect(userType.getText()).toContain('guest');
expect(valid.getText()).toContain('true');
});
it('should be invalid if empty', function() {
input('userType').enter('');
expect(binding('userType')).toEqual('');
expect(binding('myForm.input.$valid')).toEqual('false');
var userType = element(by.binding('userType'));
var valid = element(by.binding('myForm.input.$valid'));
var userInput = element(by.model('userType'));
userInput.clear();
userInput.sendKeys('');
expect(userType.getText()).toEqual('userType =');
expect(valid.getText()).toContain('false');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var formDirectiveFactory = function(isNgForm) {
+185 -112
View File
@@ -62,29 +62,31 @@ var inputType = {
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
var text = element(by.binding('text'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('text'));
it('should initialize to model', function() {
expect(binding('text')).toEqual('guest');
expect(binding('myForm.input.$valid')).toEqual('true');
expect(text.getText()).toContain('guest');
expect(valid.getText()).toContain('true');
});
it('should be invalid if empty', function() {
input('text').enter('');
expect(binding('text')).toEqual('');
expect(binding('myForm.input.$valid')).toEqual('false');
input.clear();
input.sendKeys('');
expect(text.getText()).toEqual('text =');
expect(valid.getText()).toContain('false');
});
it('should be invalid if multi word', function() {
input('text').enter('hello world');
expect(binding('myForm.input.$valid')).toEqual('false');
});
input.clear();
input.sendKeys('hello world');
it('should not be trimmed', function() {
input('text').enter('untrimmed ');
expect(binding('text')).toEqual('untrimmed ');
expect(binding('myForm.input.$valid')).toEqual('true');
expect(valid.getText()).toContain('false');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
'text': textInputType,
@@ -138,24 +140,30 @@ var inputType = {
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
var value = element(by.binding('value'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('value'));
it('should initialize to model', function() {
expect(binding('value')).toEqual('12');
expect(binding('myForm.input.$valid')).toEqual('true');
expect(value.getText()).toContain('12');
expect(valid.getText()).toContain('true');
});
it('should be invalid if empty', function() {
input('value').enter('');
expect(binding('value')).toEqual('');
expect(binding('myForm.input.$valid')).toEqual('false');
input.clear();
input.sendKeys('');
expect(value.getText()).toEqual('value =');
expect(valid.getText()).toContain('false');
});
it('should be invalid if over max', function() {
input('value').enter('123');
expect(binding('value')).toEqual('');
expect(binding('myForm.input.$valid')).toEqual('false');
input.clear();
input.sendKeys('123');
expect(value.getText()).toEqual('value =');
expect(valid.getText()).toContain('false');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
'number': numberInputType,
@@ -207,23 +215,31 @@ var inputType = {
<tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
var text = element(by.binding('text'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('text'));
it('should initialize to model', function() {
expect(binding('text')).toEqual('http://google.com');
expect(binding('myForm.input.$valid')).toEqual('true');
expect(text.getText()).toContain('http://google.com');
expect(valid.getText()).toContain('true');
});
it('should be invalid if empty', function() {
input('text').enter('');
expect(binding('text')).toEqual('');
expect(binding('myForm.input.$valid')).toEqual('false');
input.clear();
input.sendKeys('');
expect(text.getText()).toEqual('text =');
expect(valid.getText()).toContain('false');
});
it('should be invalid if not url', function() {
input('text').enter('xxx');
expect(binding('myForm.input.$valid')).toEqual('false');
input.clear();
input.sendKeys('box');
expect(valid.getText()).toContain('false');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
'url': urlInputType,
@@ -275,23 +291,30 @@ var inputType = {
<tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
var text = element(by.binding('text'));
var valid = element(by.binding('myForm.input.$valid'));
var input = element(by.model('text'));
it('should initialize to model', function() {
expect(binding('text')).toEqual('me@example.com');
expect(binding('myForm.input.$valid')).toEqual('true');
expect(text.getText()).toContain('me@example.com');
expect(valid.getText()).toContain('true');
});
it('should be invalid if empty', function() {
input('text').enter('');
expect(binding('text')).toEqual('');
expect(binding('myForm.input.$valid')).toEqual('false');
input.clear();
input.sendKeys('');
expect(text.getText()).toEqual('text =');
expect(valid.getText()).toContain('false');
});
it('should be invalid if not email', function() {
input('text').enter('xxx');
expect(binding('myForm.input.$valid')).toEqual('false');
input.clear();
input.sendKeys('xxx');
expect(valid.getText()).toContain('false');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
'email': emailInputType,
@@ -332,14 +355,17 @@ var inputType = {
</form>
Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
</doc:source>
<doc:scenario>
<doc:protractor>
it('should change state', function() {
expect(binding('color')).toEqual('"blue"');
var color = element(by.binding('color'));
input('color').select('red');
expect(binding('color')).toEqual('"red"');
expect(color.getText()).toContain('blue');
element.all(by.model('color')).get(0).click();
expect(color.getText()).toContain('red');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
'radio': radioInputType,
@@ -376,17 +402,21 @@ var inputType = {
<tt>value2 = {{value2}}</tt><br/>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should change state', function() {
expect(binding('value1')).toEqual('true');
expect(binding('value2')).toEqual('YES');
var value1 = element(by.binding('value1'));
var value2 = element(by.binding('value2'));
input('value1').check();
input('value2').check();
expect(binding('value1')).toEqual('false');
expect(binding('value2')).toEqual('NO');
expect(value1.getText()).toContain('true');
expect(value2.getText()).toContain('YES');
element(by.model('value1')).click();
element(by.model('value2')).click();
expect(value1.getText()).toContain('false');
expect(value2.getText()).toContain('NO');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
'checkbox': checkboxInputType,
@@ -394,7 +424,8 @@ var inputType = {
'hidden': noop,
'button': noop,
'submit': noop,
'reset': noop
'reset': noop,
'file': noop
};
// A helper function to call $setValidity and return the value / undefined,
@@ -417,6 +448,7 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
element.on('compositionend', function() {
composing = false;
listener();
});
}
@@ -739,44 +771,59 @@ function checkboxInputType(scope, element, attr, ctrl) {
<tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
var user = element(by.binding('{{user}}'));
var userNameValid = element(by.binding('myForm.userName.$valid'));
var lastNameValid = element(by.binding('myForm.lastName.$valid'));
var lastNameError = element(by.binding('myForm.lastName.$error'));
var formValid = element(by.binding('myForm.$valid'));
var userNameInput = element(by.model('user.name'));
var userLastInput = element(by.model('user.last'));
it('should initialize to model', function() {
expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
expect(binding('myForm.userName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
expect(user.getText()).toContain('{"name":"guest","last":"visitor"}');
expect(userNameValid.getText()).toContain('true');
expect(formValid.getText()).toContain('true');
});
it('should be invalid if empty when required', function() {
input('user.name').enter('');
expect(binding('user')).toEqual('{"last":"visitor"}');
expect(binding('myForm.userName.$valid')).toEqual('false');
expect(binding('myForm.$valid')).toEqual('false');
userNameInput.clear();
userNameInput.sendKeys('');
expect(user.getText()).toContain('{"last":"visitor"}');
expect(userNameValid.getText()).toContain('false');
expect(formValid.getText()).toContain('false');
});
it('should be valid if empty when min length is set', function() {
input('user.last').enter('');
expect(binding('user')).toEqual('{"name":"guest","last":""}');
expect(binding('myForm.lastName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
userLastInput.clear();
userLastInput.sendKeys('');
expect(user.getText()).toContain('{"name":"guest","last":""}');
expect(lastNameValid.getText()).toContain('true');
expect(formValid.getText()).toContain('true');
});
it('should be invalid if less than required min length', function() {
input('user.last').enter('xx');
expect(binding('user')).toEqual('{"name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
expect(binding('myForm.$valid')).toEqual('false');
userLastInput.clear();
userLastInput.sendKeys('xx');
expect(user.getText()).toContain('{"name":"guest"}');
expect(lastNameValid.getText()).toContain('false');
expect(lastNameError.getText()).toContain('minlength');
expect(formValid.getText()).toContain('false');
});
it('should be invalid if longer than max length', function() {
input('user.last').enter('some ridiculously long name');
expect(binding('user'))
.toEqual('{"name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
expect(binding('myForm.$valid')).toEqual('false');
userLastInput.clear();
userLastInput.sendKeys('some ridiculously long name');
expect(user.getText()).toContain('{"name":"guest"}');
expect(lastNameValid.getText()).toContain('false');
expect(lastNameError.getText()).toContain('maxlength');
expect(formValid.getText()).toContain('false');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
@@ -908,14 +955,23 @@ var VALID_CLASS = 'ng-valid',
<textarea ng-model="userContent"></textarea>
</form>
</file>
<file name="scenario.js">
<file name="protractorTest.js">
it('should data-bind and become invalid', function() {
var contentEditable = element('[contenteditable]');
if (browser.params.browser = 'safari') {
// SafariDriver can't handle contenteditable.
return;
};
var contentEditable = element(by.css('.doc-example-live [contenteditable]'));
expect(contentEditable.text()).toEqual('Change me!');
input('userContent').enter('');
expect(contentEditable.text()).toEqual('');
expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
expect(contentEditable.getText()).toEqual('Change me!');
// Firefox driver doesn't trigger the proper events on 'clear', so do this hack
contentEditable.click();
contentEditable.sendKeys(protractor.Key.chord(protractor.Key.COMMAND, "a"));
contentEditable.sendKeys(protractor.Key.BACK_SPACE);
expect(contentEditable.getText()).toEqual('');
expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
});
</file>
* </example>
@@ -968,6 +1024,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* You can override this for input directives whose concept of being empty is different to the
* default. The `checkboxInputType` directive does this because in its case a value of `false`
* implies empty.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is empty.
*/
this.$isEmpty = function(value) {
return isUndefined(value) || value === '' || value === null || value !== value;
@@ -1222,24 +1281,30 @@ var ngModelDirective = function() {
* <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
* <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
* <label for="ng-change-example2">Confirmed</label><br />
* debug = {{confirmed}}<br />
* counter = {{counter}}
* <tt>debug = {{confirmed}}</tt><br/>
* <tt>counter = {{counter}}</tt><br/>
* </div>
* </doc:source>
* <doc:scenario>
* <doc:protractor>
* var counter = element(by.binding('counter'));
* var debug = element(by.binding('confirmed'));
*
* it('should evaluate the expression if changing from view', function() {
* expect(binding('counter')).toEqual('0');
* element('#ng-change-example1').click();
* expect(binding('counter')).toEqual('1');
* expect(binding('confirmed')).toEqual('true');
* expect(counter.getText()).toContain('0');
*
* element(by.id('ng-change-example1')).click();
*
* expect(counter.getText()).toContain('1');
* expect(debug.getText()).toContain('true');
* });
*
* it('should not evaluate the expression if changing from model', function() {
* element('#ng-change-example2').click();
* expect(binding('counter')).toEqual('0');
* expect(binding('confirmed')).toEqual('true');
* element(by.id('ng-change-example2')).click();
* expect(counter.getText()).toContain('0');
* expect(debug.getText()).toContain('true');
* });
* </doc:scenario>
* </doc:protractor>
* </doc:example>
*/
var ngChangeDirective = valueFn({
@@ -1312,20 +1377,26 @@ var requiredDirective = function() {
<tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
var listInput = element(by.model('names'));
var names = element(by.binding('{{names}}'));
var valid = element(by.binding('myForm.namesInput.$valid'));
var error = element(by.css('span.error'));
it('should initialize to model', function() {
expect(binding('names')).toEqual('["igor","misko","vojta"]');
expect(binding('myForm.namesInput.$valid')).toEqual('true');
expect(element('span.error').css('display')).toBe('none');
expect(names.getText()).toContain('["igor","misko","vojta"]');
expect(valid.getText()).toContain('true');
expect(error.getCssValue('display')).toBe('none');
});
it('should be invalid if empty', function() {
input('names').enter('');
expect(binding('names')).toEqual('');
expect(binding('myForm.namesInput.$valid')).toEqual('false');
expect(element('span.error').css('display')).not().toBe('none');
});
</doc:scenario>
listInput.clear();
listInput.sendKeys('');
expect(names.getText()).toContain('');
expect(valid.getText()).toContain('false');
expect(error.getCssValue('display')).not.toBe('none'); });
</doc:protractor>
</doc:example>
*/
var ngListDirective = function() {
@@ -1407,15 +1478,17 @@ var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
<div>You chose {{my.favorite}}</div>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
var favorite = element(by.binding('my.favorite'));
it('should initialize to model', function() {
expect(binding('my.favorite')).toEqual('unicorns');
expect(favorite.getText()).toContain('unicorns');
});
it('should bind the values to the inputs', function() {
input('my.favorite').select('pizza');
expect(binding('my.favorite')).toEqual('pizza');
element.all(by.model('my.favorite')).get(0).click();
expect(favorite.getText()).toContain('pizza');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var ngValueDirective = function() {
+26 -22
View File
@@ -38,13 +38,17 @@
Hello <span ng-bind="name"></span>!
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check ng-bind', function() {
expect(using('.doc-example-live').binding('name')).toBe('Whirled');
using('.doc-example-live').input('name').enter('world');
expect(using('.doc-example-live').binding('name')).toBe('world');
var exampleContainer = $('.doc-example-live');
var nameInput = element(by.model('name'));
expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('Whirled');
nameInput.clear();
nameInput.sendKeys('world');
expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('world');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var ngBindDirective = ngDirective(function(scope, element, attr) {
@@ -90,20 +94,22 @@ var ngBindDirective = ngDirective(function(scope, element, attr) {
<pre ng-bind-template="{{salutation}} {{name}}!"></pre>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check ng-bind', function() {
expect(using('.doc-example-live').binding('salutation')).
toBe('Hello');
expect(using('.doc-example-live').binding('name')).
toBe('World');
using('.doc-example-live').input('salutation').enter('Greetings');
using('.doc-example-live').input('name').enter('user');
expect(using('.doc-example-live').binding('salutation')).
toBe('Greetings');
expect(using('.doc-example-live').binding('name')).
toBe('user');
var salutationElem = element(by.binding('salutation'));
var salutationInput = element(by.model('salutation'));
var nameInput = element(by.model('name'));
expect(salutationElem.getText()).toBe('Hello World!');
salutationInput.clear();
salutationInput.sendKeys('Greetings');
nameInput.clear();
nameInput.sendKeys('user');
expect(salutationElem.getText()).toBe('Greetings user!');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
@@ -156,12 +162,10 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
}]);
</file>
<file name="scenario.js">
<file name="protractorTest.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>'
);
expect(element(by.binding('myHTML')).getText()).toBe(
'I am an HTMLstring with links! and other stuff');
});
</file>
</example>
+32 -29
View File
@@ -114,31 +114,34 @@ function classDirective(name, selector) {
color: red;
}
</file>
<file name="scenario.js">
<file name="protractorTest.js">
var ps = element.all(by.css('.doc-example-live p'));
it('should let you toggle the class', function() {
expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/);
expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/);
expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
expect(ps.first().getAttribute('class')).not.toMatch(/red/);
input('important').check();
expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/);
element(by.model('important')).click();
expect(ps.first().getAttribute('class')).toMatch(/bold/);
input('error').check();
expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/);
element(by.model('error')).click();
expect(ps.first().getAttribute('class')).toMatch(/red/);
});
it('should let you toggle string example', function() {
expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('');
input('style').enter('red');
expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('red');
expect(ps.get(1).getAttribute('class')).toBe('');
element(by.model('style')).clear();
element(by.model('style')).sendKeys('red');
expect(ps.get(1).getAttribute('class')).toBe('red');
});
it('array example should have 3 classes', function() {
expect(element('.doc-example-live p:last').prop('className')).toBe('');
input('style1').enter('bold');
input('style2').enter('strike');
input('style3').enter('red');
expect(element('.doc-example-live p:last').prop('className')).toBe('bold strike red');
expect(ps.last().getAttribute('class')).toBe('');
element(by.model('style1')).sendKeys('bold');
element(by.model('style2')).sendKeys('strike');
element(by.model('style3')).sendKeys('red');
expect(ps.last().getAttribute('class')).toBe('bold strike red');
});
</file>
</example>
@@ -149,8 +152,8 @@ function classDirective(name, selector) {
<example animations="true">
<file name="index.html">
<input type="button" value="set" ng-click="myVar='my-class'">
<input type="button" value="clear" ng-click="myVar=''">
<input id="setbtn" type="button" value="set" ng-click="myVar='my-class'">
<input id="clearbtn" type="button" value="clear" ng-click="myVar=''">
<br>
<span class="base-class" ng-class="myVar">Sample Text</span>
</file>
@@ -165,19 +168,19 @@ function classDirective(name, selector) {
font-size:3em;
}
</file>
<file name="scenario.js">
<file name="protractorTest.js">
it('should check ng-class', function() {
expect(element('.doc-example-live span').prop('className')).not().
expect(element(by.css('.base-class')).getAttribute('class')).not.
toMatch(/my-class/);
using('.doc-example-live').element(':button:first').click();
element(by.id('setbtn')).click();
expect(element('.doc-example-live span').prop('className')).
expect(element(by.css('.base-class')).getAttribute('class')).
toMatch(/my-class/);
using('.doc-example-live').element(':button:last').click();
element(by.id('clearbtn')).click();
expect(element('.doc-example-live span').prop('className')).not().
expect(element(by.css('.base-class')).getAttribute('class')).not.
toMatch(/my-class/);
});
</file>
@@ -229,11 +232,11 @@ var ngClassDirective = classDirective('', true);
color: blue;
}
</file>
<file name="scenario.js">
<file name="protractorTest.js">
it('should check ng-class-odd and ng-class-even', function() {
expect(element('.doc-example-live li:first span').prop('className')).
expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
toMatch(/odd/);
expect(element('.doc-example-live li:last span').prop('className')).
expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
toMatch(/even/);
});
</file>
@@ -277,11 +280,11 @@ var ngClassOddDirective = classDirective('Odd', 0);
color: blue;
}
</file>
<file name="scenario.js">
<file name="protractorTest.js">
it('should check ng-class-odd and ng-class-even', function() {
expect(element('.doc-example-live li:first span').prop('className')).
expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')).
toMatch(/odd/);
expect(element('.doc-example-live li:last span').prop('className')).
expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')).
toMatch(/even/);
});
</file>
+6 -6
View File
@@ -45,14 +45,14 @@
<div id="template1" ng-cloak>{{ 'hello' }}</div>
<div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should remove the template directive and css class', function() {
expect(element('.doc-example-live #template1').attr('ng-cloak')).
not().toBeDefined();
expect(element('.doc-example-live #template2').attr('ng-cloak')).
not().toBeDefined();
expect($('.doc-example-live #template1').getAttribute('ng-cloak')).
toBeNull();
expect($('.doc-example-live #template2').getAttribute('ng-cloak')).
toBeNull();
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
*/
+52 -24
View File
@@ -82,22 +82,36 @@
</ul>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check controller as', function() {
expect(element('#ctrl-as-exmpl>:input').val()).toBe('John Smith');
expect(element('#ctrl-as-exmpl li:nth-child(1) input').val())
.toBe('408 555 1212');
expect(element('#ctrl-as-exmpl li:nth-child(2) input').val())
.toBe('john.smith@example.org');
var container = element(by.id('ctrl-as-exmpl'));
element('#ctrl-as-exmpl li:first a:contains("clear")').click();
expect(element('#ctrl-as-exmpl li:first input').val()).toBe('');
expect(container.findElement(by.model('settings.name'))
.getAttribute('value')).toBe('John Smith');
element('#ctrl-as-exmpl li:last a:contains("add")').click();
expect(element('#ctrl-as-exmpl li:nth-child(3) input').val())
.toBe('yourname@example.org');
var firstRepeat =
container.findElement(by.repeater('contact in settings.contacts').row(0));
var secondRepeat =
container.findElement(by.repeater('contact in settings.contacts').row(1));
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('408 555 1212');
expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('john.smith@example.org');
firstRepeat.findElement(by.linkText('clear')).click()
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('');
container.findElement(by.linkText('add')).click();
expect(container.findElement(by.repeater('contact in settings.contacts').row(2))
.findElement(by.model('contact.value'))
.getAttribute('value'))
.toBe('yourname@example.org');
});
</doc:scenario>
</doc:protractor>
</doc:example>
<doc:example>
<doc:source>
@@ -145,22 +159,36 @@
</ul>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check controller', function() {
expect(element('#ctrl-exmpl>:input').val()).toBe('John Smith');
expect(element('#ctrl-exmpl li:nth-child(1) input').val())
.toBe('408 555 1212');
expect(element('#ctrl-exmpl li:nth-child(2) input').val())
.toBe('john.smith@example.org');
var container = element(by.id('ctrl-exmpl'));
element('#ctrl-exmpl li:first a:contains("clear")').click();
expect(element('#ctrl-exmpl li:first input').val()).toBe('');
expect(container.findElement(by.model('name'))
.getAttribute('value')).toBe('John Smith');
element('#ctrl-exmpl li:last a:contains("add")').click();
expect(element('#ctrl-exmpl li:nth-child(3) input').val())
.toBe('yourname@example.org');
var firstRepeat =
container.findElement(by.repeater('contact in contacts').row(0));
var secondRepeat =
container.findElement(by.repeater('contact in contacts').row(1));
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('408 555 1212');
expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('john.smith@example.org');
firstRepeat.findElement(by.linkText('clear')).click()
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('');
container.findElement(by.linkText('add')).click();
expect(container.findElement(by.repeater('contact in contacts').row(2))
.findElement(by.model('contact.value'))
.getAttribute('value'))
.toBe('yourname@example.org');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
+13 -13
View File
@@ -298,8 +298,8 @@ forEach(
* Enables binding angular expressions to onsubmit events.
*
* Additionally it prevents the default action (which for form means sending the request to the
* server and reloading the current page) **but only if the form does not contain an `action`
* attribute**.
* server and reloading the current page), but only if the form does not contain `action`,
* `data-action`, or `x-action` attributes.
*
* @element form
* @priority 0
@@ -327,20 +327,20 @@ forEach(
<pre>list={{list}}</pre>
</form>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check ng-submit', function() {
expect(binding('list')).toBe('[]');
element('.doc-example-live #submit').click();
expect(binding('list')).toBe('["hello"]');
expect(input('text').val()).toBe('');
expect(element(by.binding('list')).getText()).toBe('list=[]');
element(by.css('.doc-example-live #submit')).click();
expect(element(by.binding('list')).getText()).toContain('hello');
expect(element(by.input('text')).getAttribute('value')).toBe('');
});
it('should ignore empty strings', function() {
expect(binding('list')).toBe('[]');
element('.doc-example-live #submit').click();
element('.doc-example-live #submit').click();
expect(binding('list')).toBe('["hello"]');
});
</doc:scenario>
expect(element(by.binding('list')).getText()).toBe('list=[]');
element(by.css('.doc-example-live #submit')).click();
element(by.css('.doc-example-live #submit')).click();
expect(element(by.binding('list')).getText()).toContain('hello');
});
</doc:protractor>
</doc:example>
*/
+22 -8
View File
@@ -110,19 +110,33 @@
top:50px;
}
</file>
<file name="scenario.js">
<file name="protractorTest.js">
var templateSelect = element(by.model('template'));
var includeElem = element(by.css('.doc-example-live [ng-include]'));
it('should load template1.html', function() {
expect(element('.doc-example-live [ng-include]').text()).
toMatch(/Content of template1.html/);
expect(includeElem.getText()).toMatch(/Content of template1.html/);
});
it('should load template2.html', function() {
select('template').option('1');
expect(element('.doc-example-live [ng-include]').text()).
toMatch(/Content of template2.html/);
if (browser.params.browser == 'firefox') {
// Firefox can't handle using selects
// See https://github.com/angular/protractor/issues/480
return;
}
templateSelect.click();
templateSelect.element.all(by.css('option')).get(2).click();
expect(includeElem.getText()).toMatch(/Content of template2.html/);
});
it('should change to blank', function() {
select('template').option('');
expect(element('.doc-example-live [ng-include]')).toBe(undefined);
if (browser.params.browser == 'firefox') {
// Firefox can't handle using selects
return;
}
templateSelect.click();
templateSelect.element.all(by.css('option')).get(0).click();
expect(includeElem.isPresent()).toBe(false);
});
</file>
</example>
+7 -7
View File
@@ -44,15 +44,15 @@
</div>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should alias index positions', function() {
expect(element('.example-init').text())
.toBe('list[ 0 ][ 0 ] = a;' +
'list[ 0 ][ 1 ] = b;' +
'list[ 1 ][ 0 ] = c;' +
'list[ 1 ][ 1 ] = d;');
var elements = element.all(by.css('.example-init'));
expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;');
expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;');
expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;');
expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var ngInitDirective = ngDirective({
+4 -5
View File
@@ -24,13 +24,12 @@
<div>Normal: {{1 + 2}}</div>
<div ng-non-bindable>Ignored: {{1 + 2}}</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check ng-non-bindable', function() {
expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
expect(using('.doc-example-live').element('div:last').text()).
toMatch(/1 \+ 2/);
expect(element(by.binding('1 + 2')).getText()).toContain('3');
expect(element.all(by.css('.doc-example-live div')).last().getText()).toMatch(/1 \+ 2/);
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
+40 -36
View File
@@ -123,49 +123,53 @@
</ng-pluralize>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should show correct pluralized string', function() {
expect(element('.doc-example-live ng-pluralize:first').text()).
toBe('1 person is viewing.');
expect(element('.doc-example-live ng-pluralize:last').text()).
toBe('Igor is viewing.');
var withoutOffset = element.all(by.css('ng-pluralize')).get(0);
var withOffset = element.all(by.css('ng-pluralize')).get(1);
var countInput = element(by.model('personCount'));
using('.doc-example-live').input('personCount').enter('0');
expect(element('.doc-example-live ng-pluralize:first').text()).
toBe('Nobody is viewing.');
expect(element('.doc-example-live ng-pluralize:last').text()).
toBe('Nobody is viewing.');
expect(withoutOffset.getText()).toEqual('1 person is viewing.');
expect(withOffset.getText()).toEqual('Igor is viewing.');
using('.doc-example-live').input('personCount').enter('2');
expect(element('.doc-example-live ng-pluralize:first').text()).
toBe('2 people are viewing.');
expect(element('.doc-example-live ng-pluralize:last').text()).
toBe('Igor and Misko are viewing.');
countInput.clear();
countInput.sendKeys('0');
using('.doc-example-live').input('personCount').enter('3');
expect(element('.doc-example-live ng-pluralize:first').text()).
toBe('3 people are viewing.');
expect(element('.doc-example-live ng-pluralize:last').text()).
toBe('Igor, Misko and one other person are viewing.');
expect(withoutOffset.getText()).toEqual('Nobody is viewing.');
expect(withOffset.getText()).toEqual('Nobody is viewing.');
using('.doc-example-live').input('personCount').enter('4');
expect(element('.doc-example-live ng-pluralize:first').text()).
toBe('4 people are viewing.');
expect(element('.doc-example-live ng-pluralize:last').text()).
toBe('Igor, Misko and 2 other people are viewing.');
countInput.clear();
countInput.sendKeys('2');
expect(withoutOffset.getText()).toEqual('2 people are viewing.');
expect(withOffset.getText()).toEqual('Igor and Misko are viewing.');
countInput.clear();
countInput.sendKeys('3');
expect(withoutOffset.getText()).toEqual('3 people are viewing.');
expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.');
countInput.clear();
countInput.sendKeys('4');
expect(withoutOffset.getText()).toEqual('4 people are viewing.');
expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.');
});
it('should show data-binded names', function() {
using('.doc-example-live').input('personCount').enter('4');
expect(element('.doc-example-live ng-pluralize:last').text()).
toBe('Igor, Misko and 2 other people are viewing.');
using('.doc-example-live').input('person1').enter('Di');
using('.doc-example-live').input('person2').enter('Vojta');
expect(element('.doc-example-live ng-pluralize:last').text()).
toBe('Di, Vojta and 2 other people are viewing.');
it('should show data-bound names', function() {
var withOffset = element.all(by.css('ng-pluralize')).get(1);
var personCount = element(by.model('personCount'));
var person1 = element(by.model('person1'));
var person2 = element(by.model('person2'));
personCount.clear();
personCount.sendKeys('4');
person1.clear();
person1.sendKeys('Di');
person2.clear();
person2.sendKeys('Vojta');
expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
+17 -15
View File
@@ -172,25 +172,27 @@
max-height:40px;
}
</file>
<file name="scenario.js">
it('should render initial data set', function() {
var r = using('.doc-example-live').repeater('ul li');
expect(r.count()).toBe(10);
expect(r.row(0)).toEqual(["1","John","25"]);
expect(r.row(1)).toEqual(["2","Jessie","30"]);
expect(r.row(9)).toEqual(["10","Samantha","60"]);
expect(binding('friends.length')).toBe("10");
});
<file name="protractorTest.js">
var friends = element(by.css('.doc-example-live'))
.element.all(by.repeater('friend in friends'));
it('should render initial data set', function() {
expect(friends.count()).toBe(10);
expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.');
expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.');
expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.');
expect(element(by.binding('friends.length')).getText())
.toMatch("I have 10 friends. They are:");
});
it('should update repeater when filter predicate changes', function() {
var r = using('.doc-example-live').repeater('ul li');
expect(r.count()).toBe(10);
expect(friends.count()).toBe(10);
input('q').enter('ma');
element(by.css('.doc-example-live')).element(by.model('q')).sendKeys('ma');
expect(r.count()).toBe(2);
expect(r.row(0)).toEqual(["1","Mary","28"]);
expect(r.row(1)).toEqual(["2","Samantha","60"]);
expect(friends.count()).toBe(2);
expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.');
expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.');
});
</file>
</example>
+22 -16
View File
@@ -132,16 +132,19 @@
background:white;
}
</file>
<file name="scenario.js">
it('should check ng-show / ng-hide', function() {
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
<file name="protractorTest.js">
var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up'));
var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down'));
input('checked').check();
it('should check ng-show / ng-hide', function() {
expect(thumbsUp.isDisplayed()).toBeFalsy();
expect(thumbsDown.isDisplayed()).toBeTruthy();
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
});
element(by.model('checked')).click();
expect(thumbsUp.isDisplayed()).toBeTruthy();
expect(thumbsDown.isDisplayed()).toBeFalsy();
});
</file>
</example>
*/
@@ -286,16 +289,19 @@ var ngShowDirective = ['$animate', function($animate) {
background:white;
}
</file>
<file name="scenario.js">
it('should check ng-show / ng-hide', function() {
expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1);
expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1);
<file name="protractorTest.js">
var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up'));
var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down'));
input('checked').check();
it('should check ng-show / ng-hide', function() {
expect(thumbsUp.isDisplayed()).toBeFalsy();
expect(thumbsDown.isDisplayed()).toBeTruthy();
expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1);
expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1);
});
element(by.model('checked')).click();
expect(thumbsUp.isDisplayed()).toBeTruthy();
expect(thumbsDown.isDisplayed()).toBeFalsy();
});
</file>
</example>
*/
+8 -6
View File
@@ -27,13 +27,15 @@
color: black;
}
</file>
<file name="scenario.js">
<file name="protractorTest.js">
var colorSpan = element(by.css('.doc-example-live span'));
it('should check ng-style', function() {
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
element('.doc-example-live :button[value=set]').click();
expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
element('.doc-example-live :button[value=clear]').click();
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
element(by.css('.doc-example-live input[value=set]')).click();
expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
element(by.css('.doc-example-live input[value=clear]')).click();
expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
});
</file>
</example>
+10 -7
View File
@@ -11,7 +11,7 @@
* as specified in the template.
*
* The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
* from the template cache), `ngSwitch` simply choses one of the nested elements and makes it visible based on which element
* from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
* matches the value obtained from the evaluated expression. In other words, you define a container element
* (where you place the directive), place an expression on the **`on="..."` attribute**
* (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
@@ -107,17 +107,20 @@
top:0;
}
</file>
<file name="scenario.js">
<file name="protractorTest.js">
var switchElem = element(by.css('.doc-example-live [ng-switch]'));
var select = element(by.model('selection'));
it('should start in settings', function() {
expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
expect(switchElem.getText()).toMatch(/Settings Div/);
});
it('should change to home', function() {
select('selection').option('home');
expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
select.element.all(by.css('option')).get(1).click();
expect(switchElem.getText()).toMatch(/Home Span/);
});
it('should select default', function() {
select('selection').option('other');
expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
select.element.all(by.css('option')).get(2).click();
expect(switchElem.getText()).toMatch(/default/);
});
</file>
</example>
+17 -20
View File
@@ -40,35 +40,32 @@
<pane title="{{title}}">{{text}}</pane>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should have transcluded', function() {
input('title').enter('TITLE');
input('text').enter('TEXT');
expect(binding('title')).toEqual('TITLE');
expect(binding('text')).toEqual('TEXT');
var titleElement = element(by.model('title'));
titleElement.clear();
titleElement.sendKeys('TITLE');
var textElement = element(by.model('text'));
textElement.clear();
textElement.sendKeys('TEXT');
expect(element(by.binding('title')).getText()).toEqual('TITLE');
expect(element(by.binding('text')).getText()).toEqual('TEXT');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
*/
var ngTranscludeDirective = ngDirective({
controller: ['$element', '$transclude', function($element, $transclude) {
link: function($scope, $element, $attrs, controller, $transclude) {
if (!$transclude) {
throw minErr('ngTransclude')('orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: {0}',
startingTag($element));
}
// remember the transclusion fn but call it during linking so that we don't process transclusion before directives on
// the parent element even when the transclusion replaces the current element. (we can't use priority here because
// that applies only to compile fns and not controllers
this.$transclude = $transclude;
}],
link: function($scope, $element, $attrs, controller) {
controller.$transclude(function(clone) {
$transclude(function(clone) {
$element.empty();
$element.append(clone);
});
+4 -4
View File
@@ -25,12 +25,12 @@
<a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a>
<div id="tpl-content" ng-include src="currentTpl"></div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should load template defined inside script tag', function() {
element('#tpl-link').click();
expect(element('#tpl-content').text()).toMatch(/Content of the template/);
element(by.css('#tpl-link')).click();
expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/);
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
var scriptDirective = ['$templateCache', function($templateCache) {
+10 -8
View File
@@ -29,7 +29,7 @@ var ngOptionsMinErr = minErr('ngOptions');
* option. See example below for demonstration.
*
* <div class="alert alert-warning">
* **Note:** `ngOptions` provides iterator facility for `<option>` element which should be used instead
* **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead
* of {@link ng.directive:ngRepeat ngRepeat} when you want the
* `select` model to be bound to a non-string value. This is because an option element can only
* be bound to string values at present.
@@ -120,15 +120,17 @@ var ngOptionsMinErr = minErr('ngOptions');
</div>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should check ng-options', function() {
expect(binding('{selected_color:color}')).toMatch('red');
select('color').option('0');
expect(binding('{selected_color:color}')).toMatch('black');
using('.nullable').select('color').option('');
expect(binding('{selected_color:color}')).toMatch('null');
expect(element(by.binding('{selected_color:color}')).getText()).toMatch('red');
element.all(by.select('color')).first().click();
element.all(by.css('select[ng-model="color"] option')).first().click();
expect(element(by.binding('{selected_color:color}')).getText()).toMatch('black');
element(by.css('.nullable select[ng-model="color"]')).click();
element.all(by.css('.nullable select[ng-model="color"] option')).first().click();
expect(element(by.binding('{selected_color:color}')).getText()).toMatch('null');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
+41 -20
View File
@@ -70,35 +70,47 @@
Equality <input type="checkbox" ng-model="strict"><br>
<table id="searchObjResults">
<tr><th>Name</th><th>Phone</th></tr>
<tr ng-repeat="friend in friends | filter:search:strict">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<tr ng-repeat="friendObj in friends | filter:search:strict">
<td>{{friendObj.name}}</td>
<td>{{friendObj.phone}}</td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should search across all fields when filtering with a string', function() {
input('searchText').enter('m');
expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
toEqual(['Mary', 'Mike', 'Adam']);
<doc:protractor>
var expectFriendNames = function(expectedNames, key) {
element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
arr.forEach(function(wd, i) {
expect(wd.getText()).toMatch(expectedNames[i]);
});
});
};
input('searchText').enter('76');
expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
toEqual(['John', 'Julie']);
it('should search across all fields when filtering with a string', function() {
var searchText = element(by.model('searchText'));
searchText.clear();
searchText.sendKeys('m');
expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend');
searchText.clear();
searchText.sendKeys('76');
expectFriendNames(['John', 'Julie'], 'friend');
});
it('should search in specific fields when filtering with a predicate object', function() {
input('search.$').enter('i');
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
toEqual(['Mary', 'Mike', 'Julie', 'Juliette']);
var searchAny = element(by.model('search.$'));
searchAny.clear();
searchAny.sendKeys('i');
expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj');
});
it('should use a equal comparison when comparator is true', function() {
input('search.name').enter('Julie');
input('strict').check();
expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
toEqual(['Julie']);
var searchName = element(by.model('search.name'));
var strict = element(by.model('strict'));
searchName.clear();
searchName.sendKeys('Julie');
strict.click();
expectFriendNames(['Julie'], 'friendObj');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
function filterFilter() {
@@ -124,6 +136,15 @@ function filterFilter() {
};
} else {
comparator = function(obj, text) {
if (obj && text && typeof obj === 'object' && typeof text === 'object') {
for (var objKey in obj) {
if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
comparator(obj[objKey], text[objKey])) {
return true;
}
}
return false;
}
text = (''+text).toLowerCase();
return (''+obj).toLowerCase().indexOf(text) > -1;
};
@@ -176,7 +197,7 @@ function filterFilter() {
(function(path) {
if (typeof expression[path] == 'undefined') return;
predicates.push(function(value) {
return search(path == '$' ? value : getter(value, path), expression[path]);
return search(path == '$' ? value : (value && value[path]), expression[path]);
});
})(key);
}
+40 -33
View File
@@ -24,21 +24,27 @@
</script>
<div ng-controller="Ctrl">
<input type="number" ng-model="amount"> <br>
default currency symbol ($): {{amount | currency}}<br>
custom currency identifier (USD$): {{amount | currency:"USD$"}}
default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br>
custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should init with 1234.56', function() {
expect(binding('amount | currency')).toBe('$1,234.56');
expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56');
});
it('should update', function() {
input('amount').enter('-1234');
expect(binding('amount | currency')).toBe('($1,234.00)');
expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
if (browser.params.browser == 'safari') {
// Safari does not understand the minus key. See
// https://github.com/angular/protractor/issues/481
return;
}
element(by.model('amount')).clear();
element(by.model('amount')).sendKeys('-1234');
expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
currencyFilter.$inject = ['$locale'];
@@ -77,25 +83,26 @@ function currencyFilter($locale) {
</script>
<div ng-controller="Ctrl">
Enter number: <input ng-model='val'><br>
Default formatting: {{val | number}}<br>
No fractions: {{val | number:0}}<br>
Negative number: {{-val | number:4}}
Default formatting: <span id='number-default'>{{val | number}}</span><br>
No fractions: <span>{{val | number:0}}</span><br>
Negative number: <span>{{-val | number:4}}</span>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should format numbers', function() {
expect(binding('val | number')).toBe('1,234.568');
expect(binding('val | number:0')).toBe('1,235');
expect(binding('-val | number:4')).toBe('-1,234.5679');
expect(element(by.id('number-default')).getText()).toBe('1,234.568');
expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679');
});
it('should update', function() {
input('val').enter('3374.333');
expect(binding('val | number')).toBe('3,374.333');
expect(binding('val | number:0')).toBe('3,374');
expect(binding('-val | number:4')).toBe('-3,374.3330');
});
</doc:scenario>
element(by.model('val')).clear();
element(by.model('val')).sendKeys('3374.333');
expect(element(by.id('number-default')).getText()).toBe('3,374.333');
expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
});
</doc:protractor>
</doc:example>
*/
@@ -325,22 +332,22 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
<doc:example>
<doc:source>
<span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>:
{{1288323623006 | date:'medium'}}<br>
<span>{{1288323623006 | date:'medium'}}</span><br>
<span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
<span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br>
<span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br>
<span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should format date', function() {
expect(binding("1288323623006 | date:'medium'")).
expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()).
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
dateFilter.$inject = ['$locale'];
@@ -439,11 +446,11 @@ function dateFilter($locale) {
<doc:source>
<pre>{{ {'name':'value'} | json }}</pre>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should jsonify filtered objects', function() {
expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
});
</doc:scenario>
</doc:protractor>
</doc:example>
*
*/
+23 -14
View File
@@ -36,28 +36,37 @@
<p>Output letters: {{ letters | limitTo:letterLimit }}</p>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
var numLimitInput = element(by.model('numLimit'));
var letterLimitInput = element(by.model('letterLimit'));
var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
it('should limit the number array to first three items', function() {
expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
expect(numLimitInput.getAttribute('value')).toBe('3');
expect(letterLimitInput.getAttribute('value')).toBe('3');
expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
expect(limitedLetters.getText()).toEqual('Output letters: abc');
});
it('should update the output when -3 is entered', function() {
input('numLimit').enter(-3);
input('letterLimit').enter(-3);
expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
numLimitInput.clear();
numLimitInput.sendKeys('-3');
letterLimitInput.clear();
letterLimitInput.sendKeys('-3');
expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
expect(limitedLetters.getText()).toEqual('Output letters: ghi');
});
it('should not exceed the maximum size of input array', function() {
input('numLimit').enter(100);
input('letterLimit').enter(100);
expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
numLimitInput.clear();
numLimitInput.sendKeys('100');
letterLimitInput.clear();
letterLimitInput.sendKeys('100');
expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
function limitToFilter(){
-23
View File
@@ -58,29 +58,6 @@
</table>
</div>
</doc:source>
<doc:scenario>
it('should be reverse ordered by aged', function() {
expect(binding('predicate')).toBe('-age');
expect(repeater('table.friend', 'friend in friends').column('friend.age')).
toEqual(['35', '29', '21', '19', '10']);
expect(repeater('table.friend', 'friend in friends').column('friend.name')).
toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
});
it('should reorder the table when user selects different predicate', function() {
element('.doc-example-live a:contains("Name")').click();
expect(repeater('table.friend', 'friend in friends').column('friend.name')).
toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
expect(repeater('table.friend', 'friend in friends').column('friend.age')).
toEqual(['35', '10', '29', '19', '21']);
element('.doc-example-live a:contains("Phone")').click();
expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
expect(repeater('table.friend', 'friend in friends').column('friend.name')).
toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
});
</doc:scenario>
</doc:example>
*/
orderByFilter.$inject = ['$parse'];
+24 -17
View File
@@ -587,14 +587,14 @@ function $HttpProvider() {
<option>JSONP</option>
</select>
<input type="text" ng-model="url" size="80"/>
<button ng-click="fetch()">fetch</button><br>
<button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
<button
<button id="fetchbtn" ng-click="fetch()">fetch</button><br>
<button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
<button id="samplejsonpbtn"
ng-click="updateModel('JSONP',
'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
Sample JSONP
</button>
<button
<button id="invalidjsonpbtn"
ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
Invalid JSONP
</button>
@@ -631,27 +631,34 @@ function $HttpProvider() {
<file name="http-hello.html">
Hello, $http!
</file>
<file name="scenario.js">
<file name="protractorTest.js">
var status = element(by.binding('status'));
var data = element(by.binding('data'));
var fetchBtn = element(by.id('fetchbtn'));
var sampleGetBtn = element(by.id('samplegetbtn'));
var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
it('should make an xhr GET request', function() {
element(':button:contains("Sample GET")').click();
element(':button:contains("fetch")').click();
expect(binding('status')).toBe('200');
expect(binding('data')).toMatch(/Hello, \$http!/);
sampleGetBtn.click();
fetchBtn.click();
expect(status.getText()).toMatch('200');
expect(data.getText()).toMatch(/Hello, \$http!/)
});
it('should make a JSONP request to angularjs.org', function() {
element(':button:contains("Sample JSONP")').click();
element(':button:contains("fetch")').click();
expect(binding('status')).toBe('200');
expect(binding('data')).toMatch(/Super Hero!/);
sampleJsonpBtn.click();
fetchBtn.click();
expect(status.getText()).toMatch('200');
expect(data.getText()).toMatch(/Super Hero!/);
});
it('should make JSONP request to invalid URL and invoke the error handler',
function() {
element(':button:contains("Invalid JSONP")').click();
element(':button:contains("fetch")').click();
expect(binding('status')).toBe('0');
expect(binding('data')).toBe('Request failed');
invalidJsonpBtn.click();
fetchBtn.click();
expect(status.getText()).toMatch('0');
expect(data.getText()).toMatch('Request failed');
});
</file>
</example>
+25 -7
View File
@@ -1,13 +1,18 @@
'use strict';
function createXhr(method) {
// IE8 doesn't support PATCH method, but the ActiveX object does
/* global ActiveXObject */
return (msie <= 8 && lowercase(method) === 'patch')
? new ActiveXObject('Microsoft.XMLHTTP')
: new window.XMLHttpRequest();
}
//if IE and the method is not RFC2616 compliant, or if XMLHttpRequest
//is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest
//if it is available
if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) ||
!window.XMLHttpRequest)) {
return new window.ActiveXObject("Microsoft.XMLHTTP");
} else if (window.XMLHttpRequest) {
return new window.XMLHttpRequest();
}
throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest.");
}
/**
* @ngdoc object
@@ -102,7 +107,20 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
}
if (responseType) {
xhr.responseType = responseType;
try {
xhr.responseType = responseType;
} catch (e) {
// WebKit added support for the json responseType value on 09/03/2013
// https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
// known to throw when setting the value "json" as the response type. Other older
// browsers implementing the responseType
//
// The json response type can be ignored if not supported, because JSON payloads are
// parsed on the client-side regardless.
if (responseType !== 'json') {
throw e;
}
}
}
xhr.send(post || null);
+6 -6
View File
@@ -31,11 +31,11 @@ var $interpolateMinErr = minErr('$interpolate');
//demo.label//
</div>
</doc:source>
<doc:scenario>
it('should interpolate binding with custom symbols', function() {
expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.');
});
</doc:scenario>
<doc:protractor>
it('should interpolate binding with custom symbols', function() {
expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
});
</doc:protractor>
</doc:example>
*/
function $InterpolateProvider() {
@@ -227,7 +227,7 @@ function $InterpolateProvider() {
* @description
* Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
*
* Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change
* Use {@link ng.$interpolateProvider#methods_endSymbol $interpolateProvider#endSymbol} to change
* the symbol.
*
* @returns {string} start symbol.
+1 -1
View File
@@ -574,7 +574,7 @@ function $LocationProvider(){
* @eventType broadcast on root scope
* @description
* Broadcasted before a URL will change. This change can be prevented by calling
* `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more
* `preventDefault` method of the event. See {@link ng.$rootScope.Scope#methods_$on} for more
* details about event object. Upon successful change
* {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
*
+1 -1
View File
@@ -51,7 +51,7 @@ function $LogProvider(){
* @name ng.$logProvider#debugEnabled
* @methodOf ng.$logProvider
* @description
* @param {string=} flag enable or disable debug level messages
* @param {boolean=} flag enable or disable debug level messages
* @returns {*} current value if used as getter or itself (chaining) if used as setter
*/
this.debugEnabled = function(flag) {
+8 -2
View File
@@ -73,7 +73,7 @@
* constructed via `$q.reject`, the promise will be rejected instead.
* - `reject(reason)` rejects the derived promise with the `reason`. This is equivalent to
* resolving it with a rejection constructed via `$q.reject`.
* - `notify(value)` - provides updates on the status of the promises execution. This may be called
* - `notify(value)` - provides updates on the status of the promise's execution. This may be called
* multiple times before the promise is either resolved or rejected.
*
* **Properties**
@@ -223,7 +223,7 @@ function qFactory(nextTick, exceptionHandler) {
reject: function(reason) {
deferred.resolve(reject(reason));
deferred.resolve(createInternalRejectedPromise(reason));
},
@@ -380,6 +380,12 @@ function qFactory(nextTick, exceptionHandler) {
* @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
*/
var reject = function(reason) {
var result = defer();
result.reject(reason);
return result.promise;
};
var createInternalRejectedPromise = function(reason) {
return {
then: function(callback, errback) {
var result = defer();
+2 -2
View File
@@ -926,7 +926,7 @@ function $RootScopeProvider(){
* onto the {@link ng.$exceptionHandler $exceptionHandler} service.
*
* @param {string} name Event name to emit.
* @param {...*} args Optional set of arguments which will be passed onto the event listeners.
* @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
* @return {Object} Event object (see {@link ng.$rootScope.Scope#methods_$on}).
*/
$emit: function(name, args) {
@@ -994,7 +994,7 @@ function $RootScopeProvider(){
* onto the {@link ng.$exceptionHandler $exceptionHandler} service.
*
* @param {string} name Event name to broadcast.
* @param {...*} args Optional set of arguments which will be passed onto the event listeners.
* @param {...*} args Optional one or more arguments which will be passed onto the event listeners.
* @return {Object} Event object, see {@link ng.$rootScope.Scope#methods_$on}
*/
$broadcast: function(name, args) {
+5 -3
View File
@@ -624,13 +624,15 @@ function $SceDelegateProvider() {
]
</file>
<file name="scenario.js">
<file name="protractorTest.js">
describe('SCE doc demo', function() {
it('should sanitize untrusted values', function() {
expect(element('.htmlComment').html()).toBe('<span>Is <i>anyone</i> reading this?</span>');
expect(element(by.css('.htmlComment')).getInnerHtml())
.toBe('<span>Is <i>anyone</i> reading this?</span>');
});
it('should NOT sanitize explicitly trusted values', function() {
expect(element('#explicitlyTrustedHtml').html()).toBe(
expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
'<span onmouseover="this.textContent=&quot;Explicitly trusted HTML bypasses ' +
'sanitization.&quot;">Hover over this text.</span>');
});
+3 -3
View File
@@ -31,13 +31,13 @@
<button ng-click="doGreeting(greeting)">ALERT</button>
</div>
</doc:source>
<doc:scenario>
<doc:protractor>
it('should display the greeting in the input box', function() {
input('greeting').enter('Hello, E2E Tests');
element(by.model('greeting')).sendKeys('Hello, E2E Tests');
// If we click the button it will block the test runner
// element(':button').click();
});
</doc:scenario>
</doc:protractor>
</doc:example>
*/
function $WindowProvider(){
+329 -226
View File
@@ -248,7 +248,9 @@ angular.module('ngAnimate', ['ng'])
* Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
*
*/
.factory('$$animateReflow', ['$window', '$timeout', function($window, $timeout) {
.factory('$$animateReflow', ['$window', '$timeout', '$document',
function($window, $timeout, $document) {
var bod = $document[0].body;
var requestAnimationFrame = $window.requestAnimationFrame ||
$window.webkitRequestAnimationFrame ||
function(fn) {
@@ -261,13 +263,30 @@ angular.module('ngAnimate', ['ng'])
return $timeout.cancel(timer);
};
return function(fn) {
var id = requestAnimationFrame(fn);
var id = requestAnimationFrame(function() {
var a = bod.offsetWidth + 1;
fn();
});
return function() {
cancelAnimationFrame(id);
};
};
}])
.factory('$$asyncQueueBuffer', ['$timeout', function($timeout) {
var timer, queue = [];
return function(fn) {
$timeout.cancel(timer);
queue.push(fn);
timer = $timeout(function() {
for(var i = 0; i < queue.length; i++) {
queue[i]();
}
queue = [];
}, 0, false);
};
}])
.config(['$provide', '$animateProvider', function($provide, $animateProvider) {
var noop = angular.noop;
var forEach = angular.forEach;
@@ -287,13 +306,18 @@ angular.module('ngAnimate', ['ng'])
}
}
function stripCommentsFromElement(element) {
return angular.element(extractElementNode(element));
}
function isMatchingElement(elm1, elm2) {
return extractElementNode(elm1) == extractElementNode(elm2);
}
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $timeout, $rootScope, $document) {
$provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$$asyncQueueBuffer', '$rootScope', '$document',
function($delegate, $injector, $sniffer, $rootElement, $$asyncQueueBuffer, $rootScope, $document) {
var globalAnimationCounter = 0;
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
// disable animations during bootstrap, but once we bootstrapped, wait again
@@ -315,10 +339,6 @@ angular.module('ngAnimate', ['ng'])
return classNameFilter.test(className);
};
function async(fn) {
return $timeout(fn, 0, false);
}
function lookup(name) {
if (name) {
var matches = [],
@@ -400,6 +420,7 @@ angular.module('ngAnimate', ['ng'])
this.enabled(false, element);
$delegate.enter(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
});
},
@@ -436,6 +457,7 @@ angular.module('ngAnimate', ['ng'])
cancelChildAnimations(element);
this.enabled(false, element);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('leave', 'ng-leave', element, null, null, function() {
$delegate.leave(element);
}, doneCallback);
@@ -478,6 +500,7 @@ angular.module('ngAnimate', ['ng'])
this.enabled(false, element);
$delegate.move(element, parentElement, afterElement);
$rootScope.$$postDigest(function() {
element = stripCommentsFromElement(element);
performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
});
},
@@ -513,6 +536,7 @@ angular.module('ngAnimate', ['ng'])
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
addClass : function(element, className, doneCallback) {
element = stripCommentsFromElement(element);
performAnimation('addClass', className, element, null, null, function() {
$delegate.addClass(element, className);
}, doneCallback);
@@ -549,11 +573,34 @@ angular.module('ngAnimate', ['ng'])
* @param {function()=} doneCallback the callback function that will be called once the animation is complete
*/
removeClass : function(element, className, doneCallback) {
element = stripCommentsFromElement(element);
performAnimation('removeClass', className, element, null, null, function() {
$delegate.removeClass(element, className);
}, doneCallback);
},
/**
*
* @ngdoc function
* @name ng.$animate#setClass
* @methodOf ng.$animate
* @function
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
* @param {jQuery/jqLite element} element the element which will it's CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {function=} done the callback function (if provided) that will be fired after the
* CSS classes have been set on the element
*/
setClass : function(element, add, remove, doneCallback) {
element = stripCommentsFromElement(element);
performAnimation('setClass', [add, remove], element, null, null, function() {
$delegate.setClass(element, add, remove);
}, doneCallback);
},
/**
* @ngdoc function
* @name ngAnimate.$animate#enabled
@@ -600,7 +647,15 @@ angular.module('ngAnimate', ['ng'])
and the onComplete callback will be fired once the animation is fully complete.
*/
function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
var currentClassName, classes, node = extractElementNode(element);
var classNameAdd, classNameRemove, setClassOperation = animationEvent == 'setClass';
if(setClassOperation) {
classNameAdd = className[0];
classNameRemove = className[1];
className = classNameAdd + ' ' + classNameRemove;
}
var currentClassName, classes, node = element[0];
if(node) {
currentClassName = node.className;
classes = currentClassName + ' ' + className;
@@ -612,18 +667,27 @@ angular.module('ngAnimate', ['ng'])
fireDOMOperation();
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
closeAnimation();
fireDoneCallbackAsync();
return;
}
var elementEvents = angular.element._data(node);
elementEvents = elementEvents && elementEvents.events;
var animationLookup = (' ' + classes).replace(/\s+/g,'.');
if (!parentElement) {
parentElement = afterElement ? afterElement.parent() : element.parent();
}
var matches = lookup(animationLookup);
var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass';
var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
var matches = lookup(animationLookup);
var isClassBased = animationEvent == 'addClass' ||
animationEvent == 'removeClass' ||
setClassOperation;
var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
var runningAnimations = ngAnimateState.active || {};
var totalActiveAnimations = ngAnimateState.totalActive || 0;
var lastAnimation = ngAnimateState.last;
//skip the animation if animations are disabled, a parent is already being animated,
//the element is not currently attached to the document body or then completely close
@@ -642,7 +706,7 @@ angular.module('ngAnimate', ['ng'])
//only add animations if the currently running animation is not structural
//or if there is no animation running at all
var allowAnimations = isClassBased ?
!ngAnimateState.disabled && (!ngAnimateState.running || !ngAnimateState.structural) :
!ngAnimateState.disabled && (!lastAnimation || lastAnimation.classBased) :
true;
if(allowAnimations) {
@@ -677,55 +741,48 @@ angular.module('ngAnimate', ['ng'])
return;
}
var ONE_SPACE = ' ';
//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 = ONE_SPACE + currentClassName + ONE_SPACE;
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);
var skipAnimation = false;
if(totalActiveAnimations > 0) {
var animationsToCancel = [];
if(!isClassBased) {
if(animationEvent == 'leave' && runningAnimations['ng-leave']) {
skipAnimation = true;
} else {
//cancel all animations when a structural animation takes place
for(var klass in runningAnimations) {
animationsToCancel.push(runningAnimations[klass]);
cleanup(element, klass);
}
runningAnimations = {};
totalActiveAnimations = 0;
}
} else if(lastAnimation.event == 'setClass') {
animationsToCancel.push(lastAnimation);
cleanup(element, className);
}
else if(runningAnimations[className]) {
var current = runningAnimations[className];
if(current.event == animationEvent) {
skipAnimation = true;
} else {
animationsToCancel.push(current);
cleanup(element, className);
}
}
//in the event that the CSS is class is quickly added and removed back
//then we don't want to wait until after the reflow to add/remove the CSS
//class since both class animations may run into a race condition.
//The code below will check to see if that is occurring and will
//immediately remove the former class before the reflow so that the
//animation can snap back to the original animation smoothly
var isFullyClassBasedAnimation = isClassBased && !ngAnimateState.structural;
var isRevertingClassAnimation = isFullyClassBasedAnimation &&
ngAnimateState.className == className &&
animationEvent != ngAnimateState.event;
//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 || isRevertingClassAnimation) {
(ngAnimateState.done || noop)(true);
} else if(isFullyClassBasedAnimation) {
//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(ONE_SPACE + ngAnimateState.className + ONE_SPACE, ONE_SPACE) :
futureClassName + ngAnimateState.className + ONE_SPACE;
if(animationsToCancel.length > 0) {
angular.forEach(animationsToCancel, function(operation) {
(operation.done || noop)(true);
cancelAnimations(operation.animations);
});
}
}
//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.
var classNameToken = ONE_SPACE + className + ONE_SPACE;
if((animationEvent == 'addClass' && futureClassName.indexOf(classNameToken) >= 0) ||
(animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
fireDOMOperation();
if(isClassBased && !setClassOperation && !skipAnimation) {
skipAnimation = (animationEvent == 'addClass') == element.hasClass(className); //opposite of XOR
}
if(skipAnimation) {
fireBeforeCallbackAsync();
fireAfterCallbackAsync();
fireDoneCallbackAsync();
@@ -736,13 +793,22 @@ angular.module('ngAnimate', ['ng'])
//parent animations to find and cancel child animations when needed
element.addClass(NG_ANIMATE_CLASS_NAME);
element.data(NG_ANIMATE_STATE, {
running:true,
event:animationEvent,
className:className,
structural:!isClassBased,
animations:animations,
var localAnimationCount = globalAnimationCounter++;
lastAnimation = {
classBased : isClassBased,
event : animationEvent,
animations : animations,
done:onBeforeAnimationsComplete
};
totalActiveAnimations++;
runningAnimations[className] = lastAnimation;
element.data(NG_ANIMATE_STATE, {
last : lastAnimation,
active : runningAnimations,
index : localAnimationCount,
totalActive : totalActiveAnimations
});
//first we run the before animations and when all of those are complete
@@ -750,6 +816,11 @@ angular.module('ngAnimate', ['ng'])
invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
function onBeforeAnimationsComplete(cancelled) {
var data = element.data(NG_ANIMATE_STATE);
cancelled = cancelled ||
!data || !data.active[className] ||
(isClassBased && data.active[className].event != animationEvent);
fireDOMOperation();
if(cancelled === true) {
closeAnimation();
@@ -759,11 +830,8 @@ angular.module('ngAnimate', ['ng'])
//set the done function to the final done function
//so that the DOM event won't be executed twice by accident
//if the after animation is cancelled as well
var data = element.data(NG_ANIMATE_STATE);
if(data) {
data.done = closeAnimation;
element.data(NG_ANIMATE_STATE, data);
}
var currentAnimation = data.active[className];
currentAnimation.done = closeAnimation;
invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
}
@@ -786,9 +854,13 @@ angular.module('ngAnimate', ['ng'])
}
if(animation[phase]) {
animation[endFnName] = isClassBased ?
animation[phase](element, className, animationPhaseCompleted) :
animation[phase](element, animationPhaseCompleted);
if(setClassOperation) {
animation[endFnName] = animation[phase](element, classNameAdd, classNameRemove, animationPhaseCompleted);
} else {
animation[endFnName] = isClassBased ?
animation[phase](element, className, animationPhaseCompleted) :
animation[phase](element, animationPhaseCompleted);
}
} else {
animationPhaseCompleted();
}
@@ -809,29 +881,32 @@ angular.module('ngAnimate', ['ng'])
}
function fireDOMCallback(animationPhase) {
element.triggerHandler('$animate:' + animationPhase, {
event : animationEvent,
className : className
});
var eventName = '$animate:' + animationPhase;
if(elementEvents && elementEvents[eventName] && elementEvents[eventName].length > 0) {
$$asyncQueueBuffer(function() {
element.triggerHandler(eventName, {
event : animationEvent,
className : className
});
});
}
}
function fireBeforeCallbackAsync() {
async(function() {
fireDOMCallback('before');
});
fireDOMCallback('before');
}
function fireAfterCallbackAsync() {
async(function() {
fireDOMCallback('after');
});
fireDOMCallback('after');
}
function fireDoneCallbackAsync() {
async(function() {
fireDOMCallback('close');
doneCallback && doneCallback();
});
fireDOMCallback('close');
if(doneCallback) {
$$asyncQueueBuffer(function() {
doneCallback();
});
}
}
//it is less complicated to use a flag than managing and cancelling
@@ -853,10 +928,13 @@ angular.module('ngAnimate', ['ng'])
failing would be when a parent HTML tag has a ng-class attribute
causing ALL directives below to skip animations during the digest */
if(isClassBased) {
cleanup(element);
cleanup(element, className);
} else {
data.closeAnimationTimeout = async(function() {
cleanup(element);
$$asyncQueueBuffer(function() {
var data = element.data(NG_ANIMATE_STATE) || {};
if(localAnimationCount == data.index) {
cleanup(element, className, animationEvent);
}
});
element.data(NG_ANIMATE_STATE, data);
}
@@ -871,9 +949,11 @@ angular.module('ngAnimate', ['ng'])
forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
element = angular.element(element);
var data = element.data(NG_ANIMATE_STATE);
if(data) {
cancelAnimations(data.animations);
cleanup(element);
if(data && data.active) {
angular.forEach(data.active, function(operation) {
(operation.done || noop)(true);
cancelAnimations(operation.animations);
});
}
});
}
@@ -890,15 +970,27 @@ angular.module('ngAnimate', ['ng'])
});
}
function cleanup(element) {
function cleanup(element, className) {
if(isMatchingElement(element, $rootElement)) {
if(!rootAnimateState.disabled) {
rootAnimateState.running = false;
rootAnimateState.structural = false;
}
} else {
element.removeClass(NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE);
} else if(className) {
var data = element.data(NG_ANIMATE_STATE) || {};
var removeAnimations = className === true;
if(!removeAnimations) {
if(data.active && data.active[className]) {
data.totalActive--;
delete data.active[className];
}
}
if(removeAnimations || !data.totalActive) {
element.removeClass(NG_ANIMATE_CLASS_NAME);
element.removeData(NG_ANIMATE_STATE);
}
}
}
@@ -917,7 +1009,7 @@ angular.module('ngAnimate', ['ng'])
var isRoot = isMatchingElement(parentElement, $rootElement);
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
var result = state && (!!state.disabled || !!state.running);
var result = state && (!!state.disabled || state.running || state.totalActive > 0);
if(isRoot || result) {
return result;
}
@@ -967,74 +1059,57 @@ angular.module('ngAnimate', ['ng'])
var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
var NG_ANIMATE_BLOCK_CLASS_NAME = 'ng-animate-block-transitions';
var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
var CLOSING_TIME_BUFFER = 1.5;
var ONE_SECOND = 1000;
var animationCounter = 0;
var lookupCache = {};
var parentCounter = 0;
var animationReflowQueue = [];
var animationElementQueue = [];
var cancelAnimationReflow;
var closingAnimationTime = 0;
var timeOut = false;
function afterReflow(element, callback) {
if(cancelAnimationReflow) {
cancelAnimationReflow();
}
animationReflowQueue.push(callback);
var node = extractElementNode(element);
element = angular.element(node);
animationElementQueue.push(element);
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
var stagger = elementData.stagger;
var staggerTime = elementData.itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
var animationTime = (elementData.maxDelay + elementData.maxDuration) * CLOSING_TIME_BUFFER;
closingAnimationTime = Math.max(closingAnimationTime, (staggerTime + animationTime) * ONE_SECOND);
//by placing a counter we can avoid an accidental
//race condition which may close an animation when
//a follow-up animation is midway in its animation
elementData.animationCount = animationCounter;
cancelAnimationReflow = $$animateReflow(function() {
forEach(animationReflowQueue, function(fn) {
fn();
});
//copy the list of elements so that successive
//animations won't conflict if they're added before
//the closing animation timeout has run
var elementQueueSnapshot = [];
var animationCounterSnapshot = animationCounter;
forEach(animationElementQueue, function(elm) {
elementQueueSnapshot.push(elm);
});
$timeout(function() {
closeAllAnimations(elementQueueSnapshot, animationCounterSnapshot);
elementQueueSnapshot = null;
}, closingAnimationTime, false);
animationReflowQueue = [];
animationElementQueue = [];
cancelAnimationReflow = null;
lookupCache = {};
closingAnimationTime = 0;
animationCounter++;
});
}
function closeAllAnimations(elements, count) {
var closingTimer = null;
var closingTimestamp = 0;
var animationElementQueue = [];
function animationCloseHandler(element, totalTime) {
var futureTimestamp = Date.now() + (totalTime * 1000);
if(futureTimestamp <= closingTimestamp) {
return;
}
$timeout.cancel(closingTimer);
var node = extractElementNode(element);
element = angular.element(node);
animationElementQueue.push(element);
closingTimestamp = futureTimestamp;
closingTimer = $timeout(function() {
closeAllAnimations(animationElementQueue);
animationElementQueue = [];
}, totalTime, false);
}
function closeAllAnimations(elements) {
forEach(elements, function(element) {
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(elementData && elementData.animationCount == count) {
if(elementData) {
(elementData.closeAnimationFn || noop)();
}
});
@@ -1119,12 +1194,12 @@ angular.module('ngAnimate', ['ng'])
return parentID + '-' + extractElementNode(element).className;
}
function animateSetup(element, className, calculationDecorator) {
function animateSetup(animationEvent, element, className, calculationDecorator) {
var cacheKey = getCacheKey(element);
var eventCacheKey = cacheKey + ' ' + className;
var stagger = {};
var itemIndex = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
var stagger = {};
if(itemIndex > 0) {
var staggerClassName = className + '-stagger';
var staggerCacheKey = cacheKey + ' ' + staggerClassName;
@@ -1144,60 +1219,63 @@ angular.module('ngAnimate', ['ng'])
element.addClass(className);
var formerData = element.data(NG_ANIMATE_CSS_DATA_KEY) || {};
var timings = calculationDecorator(function() {
return getElementAnimationDetails(element, eventCacheKey);
});
/* there is no point in performing a reflow if the animation
timeout is empty (this would cause a flicker bug normally
in the page. There is also no point in performing an animation
that only has a delay and no duration */
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
if(maxDuration === 0) {
var transitionDuration = timings.transitionDuration;
var animationDuration = timings.animationDuration;
if(transitionDuration === 0 && animationDuration === 0) {
element.removeClass(className);
return false;
}
element.data(NG_ANIMATE_CSS_DATA_KEY, {
running : formerData.running || 0,
itemIndex : itemIndex,
stagger : stagger,
timings : timings,
closeAnimationFn : angular.noop
});
//temporarily disable the transition so that the enter styles
//don't animate twice (this is here to avoid a bug in Chrome/FF).
var activeClassName = '';
timings.transitionDuration > 0 ?
blockTransitions(element) :
var isCurrentlyAnimating = formerData.running > 0 || animationEvent == 'setClass';
if(transitionDuration > 0) {
blockTransitions(element, className, isCurrentlyAnimating);
}
if(animationDuration > 0) {
blockKeyframeAnimations(element);
forEach(className.split(' '), function(klass, i) {
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
});
element.data(NG_ANIMATE_CSS_DATA_KEY, {
className : className,
activeClassName : activeClassName,
maxDuration : maxDuration,
maxDelay : maxDelay,
classes : className + ' ' + activeClassName,
timings : timings,
stagger : stagger,
itemIndex : itemIndex
});
}
return true;
}
function blockTransitions(element) {
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
function isStructuralAnimation(className) {
return className == 'ng-enter' || className == 'ng-move' || className == 'ng-leave';
}
function blockTransitions(element, className, isAnimating) {
if(isStructuralAnimation(className) || !isAnimating) {
extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
} else {
element.addClass(NG_ANIMATE_BLOCK_CLASS_NAME);
}
}
function blockKeyframeAnimations(element) {
extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
}
function unblockTransitions(element) {
function unblockTransitions(element, className) {
var prop = TRANSITION_PROP + PROPERTY_KEY;
var node = extractElementNode(element);
if(node.style[prop] && node.style[prop].length > 0) {
node.style[prop] = '';
}
element.removeClass(NG_ANIMATE_BLOCK_CLASS_NAME);
}
function unblockKeyframeAnimations(element) {
@@ -1208,22 +1286,28 @@ angular.module('ngAnimate', ['ng'])
}
}
function animateRun(element, className, activeAnimationComplete) {
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
function animateRun(animationEvent, element, className, activeAnimationComplete) {
var node = extractElementNode(element);
var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(node.className.indexOf(className) == -1 || !elementData) {
activeAnimationComplete();
return;
}
var timings = elementData.timings;
var activeClassName = '';
forEach(className.split(' '), function(klass, i) {
activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
});
var stagger = elementData.stagger;
var maxDuration = elementData.maxDuration;
var activeClassName = elementData.activeClassName;
var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * ONE_SECOND;
var timings = elementData.timings;
var itemIndex = elementData.itemIndex;
var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
var maxDelayTime = maxDelay * ONE_SECOND;
var startTime = Date.now();
var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
var itemIndex = elementData.itemIndex;
var style = '', appliedStyles = [];
if(timings.transitionDuration > 0) {
@@ -1265,6 +1349,13 @@ angular.module('ngAnimate', ['ng'])
onEnd();
activeAnimationComplete();
};
var staggerTime = itemIndex * (Math.max(stagger.animationDelay, stagger.transitionDelay) || 0);
var animationTime = (maxDelay + maxDuration) * CLOSING_TIME_BUFFER;
var totalTime = (staggerTime + animationTime) * ONE_SECOND;
elementData.running++;
animationCloseHandler(element, totalTime);
return onEnd;
// This will automatically be called by $animate so
@@ -1284,7 +1375,7 @@ angular.module('ngAnimate', ['ng'])
event.stopPropagation();
var ev = event.originalEvent || event;
var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
/* Firefox (or possibly just Gecko) likes to not round values up
* when a ms measurement is used for the animation */
var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
@@ -1311,28 +1402,28 @@ angular.module('ngAnimate', ['ng'])
return style;
}
function animateBefore(element, className, calculationDecorator) {
if(animateSetup(element, className, calculationDecorator)) {
function animateBefore(animationEvent, element, className, calculationDecorator) {
if(animateSetup(animationEvent, element, className, calculationDecorator)) {
return function(cancelled) {
cancelled && animateClose(element, className);
};
}
}
function animateAfter(element, className, afterAnimationComplete) {
function animateAfter(animationEvent, element, className, afterAnimationComplete) {
if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
return animateRun(element, className, afterAnimationComplete);
return animateRun(animationEvent, element, className, afterAnimationComplete);
} else {
animateClose(element, className);
afterAnimationComplete();
}
}
function animate(element, className, animationComplete) {
function animate(animationEvent, element, className, animationComplete) {
//If the animateSetup function doesn't bother returning a
//cancellation function then it means that there is no animation
//to perform at all
var preReflowCancellation = animateBefore(element, className);
var preReflowCancellation = animateBefore(animationEvent, element, className);
if(!preReflowCancellation) {
animationComplete();
return;
@@ -1345,12 +1436,12 @@ angular.module('ngAnimate', ['ng'])
//happen in the first place
var cancel = preReflowCancellation;
afterReflow(element, function() {
unblockTransitions(element);
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
//once the reflow is complete then we point cancel to
//the new cancellation function which will remove all of the
//animation properties from the active animation
cancel = animateAfter(element, className, animationComplete);
cancel = animateAfter(animationEvent, element, className, animationComplete);
});
return function(cancelled) {
@@ -1360,54 +1451,59 @@ angular.module('ngAnimate', ['ng'])
function animateClose(element, className) {
element.removeClass(className);
element.removeData(NG_ANIMATE_CSS_DATA_KEY);
var data = element.data(NG_ANIMATE_CSS_DATA_KEY);
if(data) {
if(data.running) {
data.running--;
}
if(!data.running || data.running === 0) {
element.removeData(NG_ANIMATE_CSS_DATA_KEY);
}
}
}
return {
allowCancel : function(element, animationEvent, className) {
//always cancel the current animation if it is a
//structural animation
var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes;
if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) {
return true;
}
var parentElement = element.parent();
var clone = angular.element(extractElementNode(element).cloneNode());
//make the element super hidden and override any CSS style values
clone.attr('style','position:absolute; top:-9999px; left:-9999px');
clone.removeAttr('id');
clone.empty();
forEach(oldClasses.split(' '), function(klass) {
clone.removeClass(klass);
});
var suffix = animationEvent == 'addClass' ? '-add' : '-remove';
clone.addClass(suffixClasses(className, suffix));
parentElement.append(clone);
var timings = getElementAnimationDetails(clone);
clone.remove();
return Math.max(timings.transitionDuration, timings.animationDuration) > 0;
},
enter : function(element, animationCompleted) {
return animate(element, 'ng-enter', animationCompleted);
return animate('enter', element, 'ng-enter', animationCompleted);
},
leave : function(element, animationCompleted) {
return animate(element, 'ng-leave', animationCompleted);
return animate('leave', element, 'ng-leave', animationCompleted);
},
move : function(element, animationCompleted) {
return animate(element, 'ng-move', animationCompleted);
return animate('move', element, 'ng-move', animationCompleted);
},
beforeSetClass : function(element, add, remove, animationCompleted) {
var className = suffixClasses(remove, '-remove') + ' ' +
suffixClasses(add, '-add');
var cancellationMethod = animateBefore('setClass', element, className, function(fn) {
/* when classes are removed from an element then the transition style
* that is applied is the transition defined on the element without the
* CSS class being there. This is how CSS3 functions outside of ngAnimate.
* http://plnkr.co/edit/j8OzgTNxHTb4n3zLyjGW?p=preview */
var klass = element.attr('class');
element.removeClass(remove);
element.addClass(add);
var timings = fn();
element.attr('class', klass);
return timings;
});
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
animationCompleted();
});
return cancellationMethod;
}
animationCompleted();
},
beforeAddClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'), function(fn) {
var cancellationMethod = animateBefore('addClass', element, suffixClasses(className, '-add'), function(fn) {
/* when a CSS class is added to an element then the transition style that
* is applied is the transition defined on the element when the CSS class
@@ -1421,7 +1517,7 @@ angular.module('ngAnimate', ['ng'])
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
animationCompleted();
});
@@ -1430,12 +1526,19 @@ angular.module('ngAnimate', ['ng'])
animationCompleted();
},
setClass : function(element, add, remove, animationCompleted) {
remove = suffixClasses(remove, '-remove');
add = suffixClasses(add, '-add');
var className = remove + ' ' + add;
return animateAfter('setClass', element, className, animationCompleted);
},
addClass : function(element, className, animationCompleted) {
return animateAfter(element, suffixClasses(className, '-add'), animationCompleted);
return animateAfter('addClass', element, suffixClasses(className, '-add'), animationCompleted);
},
beforeRemoveClass : function(element, className, animationCompleted) {
var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'), function(fn) {
var cancellationMethod = animateBefore('removeClass', element, suffixClasses(className, '-remove'), function(fn) {
/* when classes are removed from an element then the transition style
* that is applied is the transition defined on the element without the
* CSS class being there. This is how CSS3 functions outside of ngAnimate.
@@ -1449,7 +1552,7 @@ angular.module('ngAnimate', ['ng'])
if(cancellationMethod) {
afterReflow(element, function() {
unblockTransitions(element);
unblockTransitions(element, className);
unblockKeyframeAnimations(element);
animationCompleted();
});
@@ -1459,7 +1562,7 @@ angular.module('ngAnimate', ['ng'])
},
removeClass : function(element, className, animationCompleted) {
return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted);
return animateAfter('removeClass', element, suffixClasses(className, '-remove'), animationCompleted);
}
};
+2 -2
View File
@@ -17,7 +17,7 @@ $provide.value("$locale", {
"\u0161e\u0161tadienis"
],
"MONTH": [
"sausio",
"sausis",
"vasaris",
"kovas",
"balandis",
@@ -41,7 +41,7 @@ $provide.value("$locale", {
],
"SHORTMONTH": [
"Saus.",
"Vas",
"Vas.",
"Kov.",
"Bal.",
"Geg.",
+2 -2
View File
@@ -17,7 +17,7 @@ $provide.value("$locale", {
"\u0161e\u0161tadienis"
],
"MONTH": [
"sausio",
"sausis",
"vasaris",
"kovas",
"balandis",
@@ -41,7 +41,7 @@ $provide.value("$locale", {
],
"SHORTMONTH": [
"Saus.",
"Vas",
"Vas.",
"Kov.",
"Bal.",
"Geg.",

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