Compare commits

...

122 Commits

Author SHA1 Message Date
Peter Bacon Darwin 59205d7809 chore(bower/publish): run local precommit script if available
Closes #11164
2015-02-24 17:22:45 +00:00
Martin Staffa 0cf170df16 chore(grunt): use path.normalize in grunt shell:npm-install
This makes the command runnable on Windows clients.
2015-02-23 20:51:37 +01:00
Tero Parviainen 05a3b088fd docs(ngRepeat): extend description of tracking and duplicates
Add a section to the documentation on how tracking between items and DOM
elements is done, and why duplicates are not allowed in the collection.

Describe how the default tracking behaviour can be substituted with track by.

Tweak the wording in the `track by` section to discuss “tracking expressions”
instead of “tracking functions”.

Closes #8153
2015-02-23 20:17:09 +01:00
Martin Staffa 65df5da07a docs(ngDisabled): clarify the explanation of attributes & interpolation
Closes #11032
Closes #11133
2015-02-22 22:17:22 +01:00
Igor Minar 0689c3697f chore(npm): update dependencies 2015-02-21 19:03:50 -08:00
Igor Minar 0f53c29954 chore(travis,grunt): extract the npm install and cache busting logic into install-dependencies.sh
So now we are DRY.

Added extra error checking and improved the grunt file init setup so that stdio is visible in console.

Closes #11110
2015-02-21 18:55:19 -08:00
Igor Minar 11bfe85598 chore(grunt): blow away cached node_modules when npm-shrinkwrap.json changes
this replicates the travis setup in grunt from the previous commit

the reason why we duplicate this rather than having just a single place for this code is so that
we can individually time the actions on travis
2015-02-21 18:55:07 -08:00
Igor Minar 93c503fa16 chore(travis): don't break the build when travis cache is empty
`du` returns error code 2 when any of the directories don't exist which breaks the build.

this scenario is common when the cache was emptied or when travis is building forks that don't have travis cache enabled
2015-02-21 18:54:55 -08:00
Igor Minar 9d4e948e82 chore(travis): blow away cached node_modules when npm-shrinkwrap.json changes
`npm install` blindly accepts the node_modules cache and doesn't verify if it matches requirements in the current npm-shrinkwrap.json.

This means that if we are using travis cache and npm-shrinkwrap.json changes npm will keep on using the old dependencies, in spite of the guarantees that shrinkwrap claims to offer.

https://github.com/angular/angular.js/pull/11110#issuecomment-75302946

With this change, we will blow away the node_modules directory if the shrinkwrap changes compared to the one
used to populate node_modules.
2015-02-21 18:54:24 -08:00
Igor Minar 26f19d0399 chore(karma): use karma 0.12.31 instead of a custom fork
Previously Vojta set us up to use a custom fork of Karma that used socket.io 1.3.4. This change moves us to an official release of Karma but downgrades socket.io back to 0.9.16.

We now need to hurry up and finish the socket.io upgrade in karma which was blocked on shrinkwrap issues in Angular that are resolved with the previous few commits in this PR.

Conflicts:
	npm-shrinkwrap.clean.json
	npm-shrinkwrap.json
2015-02-21 18:54:08 -08:00
Igor Minar 5cd2e2291b chore(clean-shrinkwrap): drop from property from the clean shrinkwrap
it usually contains urls to temp directories which are not interesting, the info
we do want to preserve is in the `resolved` property and we do keep that one.

Conflicts:
	npm-shrinkwrap.clean.json
2015-02-21 18:41:10 -08:00
Igor Minar 69bbbe675e chore(clean-shrinkwrap): preserve git+https:// resolved property
previously we thought that git:// was enough, but we also want git+https:// otherwise we can miss important info
in clean shrinkwrap file

Conflicts:
	npm-shrinkwrap.clean.json
2015-02-21 18:40:11 -08:00
Igor Minar 825de1cdf2 chore(npm/travis): upgrade to npm 2.5 and require it via package.json
currently karma's dependencies don't install on node 0.12 or io.js so we'll just update npm in hope that that
this will mitigate "cb() never called!" erorrs. See: https://travis-ci.org/angular/angular.js/jobs/51474043#L2181
2015-02-21 18:40:11 -08:00
Igor Minar e5318c61ee chore(npm): don't clean npm-shrinkwrap.json instead generate npm-shrinkwrap.clean.json
Previously we would clean up npm-shrinkwarp.json file in order to achieve serialization
stability, which would then allow us to create human readable diffs that allow code reviews
of npm-shrinkwrap to be meaningful.

This cleanup process does have an impact on the functionality of npm which was only recently
discovered by Vojta, when we tried to update to new Karma version. See: Automattic/engine.io-client#370

According to Julie, the root cause of these issues is npm/npm/#3581.

The workaround implemented in this commit is not to interfere with npm-shrinkwrap.json file, but instead
preserve the cleaned up version of its content in npm-shrinkwrap.clean.json which can then be used to
produce human readable diffs for code reviews of npm dependency updates.
2015-02-21 18:37:56 -08:00
Igor Minar 67b40a19fb chore(travis): turn on caching for node_modules and bower_components directories
The cache behavior is documented at http://docs.travis-ci.com/user/caching/

This commit also disabled our custom caching via npm-bundler-deps.sh
2015-02-21 18:36:37 -08:00
Martin Staffa 5fbac749c9 docs($resource): fix list level
Closes #11055
2015-02-13 23:54:11 +01:00
Julie Ralph 0daadeb3d3 chore(ci): make all browserstack tests allowed failures 2015-02-13 10:20:34 -08:00
Peter Bacon Darwin 6b7625a095 fix(ngModel): fix issues when parserName is same as validator key
For $validate(), it is necessary to store the parseError state
in the controller. Otherwise, if the parser name equals a validator
key, $validate() will assume a parse error occured if the validator
is invalid.

Also, setting the validity for the parser now happens after setting
validity for the validator key. Otherwise, the parse key is set,
and then immediately afterwards the validator key is unset
(because parse errors remove all other validations).

Fixes #10698
Closes #10850
Closes #11046
2015-02-13 12:34:44 +00:00
Rouven Weßling ab7e0cd100 refactor(ngSanitize): remove workarounds for IE8
Closes #10758
2015-02-13 11:52:05 +00:00
Peter Bacon Darwin dee5f7fea5 test(ngSanitize): add tests for decodeEntities 2015-02-13 11:52:05 +00:00
Julie Ralph 140d149cee chore(ci): update to use the latest sauce connect (4.3 to 4.3.6) 2015-02-12 13:19:42 -08:00
Peter Bacon Darwin 4d65ddddf8 docs(FAQ): update the zipped file size 2015-02-12 11:59:53 +00:00
Tobyee abfce5327c fix(input): create max and/or min validator regardless of initial value
Also adds corresponding tests for ngMin / ngMax.

Fixes #10307
Closes #10327
2015-02-10 23:39:49 +01:00
Martin Staffa 944c150e6c fix(ngAria): correctly set "checked" attr for checkboxes and radios
Make sure the checked attribute is set correctly for:
- checkboxes with string and integer models using ngTrueValue /
ngFalseValue
- radios with integer models
- radios with boolean models using ngValue

Fixes #10389
Fixes #10212
2015-02-10 22:35:36 +01:00
Peter Bacon Darwin d8dc53d215 chore(CHANGELOG): add release name! 2015-02-09 11:16:46 +00:00
Peter Bacon Darwin 0ec206f5a6 chore(CHANGELOG): update to 1.4.0-beta.4 and 1.3.13 2015-02-09 10:39:03 +00:00
Lucas Galfaso ce49d4d61b fix(sanitize): handle newline characters inside <script> for IE
Tweak the regex used match characters inside <script> and <style> to a IE
compatible regex.
2015-02-07 19:21:53 +01:00
Marcy Sutton 69ee593fd2 fix(ngAria): ensure native controls fire a single click
Closes #10388
Closes #10766
2015-02-07 10:27:29 +00:00
Shahar Talmi 39ddef6829 fix(ngMock): handle cases where injector is created before tests
This caused an exception for people who created an injector before the tests actually began to run. Since the array was initialized only in beforeEach, anyone accessing it before that would throw. This is solved easily but initializing the array immediately.

Closes #10967
2015-02-06 03:47:32 +02:00
Lucas Galfaso 11aedbd741 fix(sanitize): handle newline characters inside special tags
Handle newlines characters when they are present inside <script> and <style> tags.

Closes #10943
2015-02-04 15:28:49 +01:00
Lukas Elmer e4d1e12f7f chore(docs): fix nav scrolling for non-mobile screens
Closes #10936
2015-02-04 13:37:28 +00:00
Morris Singer 9c2b32d6f3 docs(guide/forms): improve wording
Closes #01937
2015-02-04 13:17:07 +00:00
dtritus 4b3a590b00 fix($location): prevent page reload if initial url has empty hash at the end
If initial url has empty hash at the end, $location replaces it with url
without hash causing an unwanted page reload.

Closes #10397
Closes #10960
2015-02-04 12:49:20 +00:00
Peter Bacon Darwin 7f50e97628 docs(CHANGELOG): update changelog for 1.4.0-beta.3 and 1.3.12 2015-02-03 20:53:22 +00:00
Peter Bacon Darwin e5ee6123fc docs(CHANGELOG): update changelog for 1.4.0-beta.3 and 1.3.12 2015-02-02 20:26:52 +00:00
Chris 923b6aba0d docs(guide): update "AngularJS" book link
Added latest 2014 revision "AngularJS: Up and Running" with Amazon link.
"AngularJS" was previous 2013 version.

Closes #10920
2015-02-02 14:03:17 +00:00
Hannah Howard 955e20eb61 fix ($compile): keep prototype properties for template URL directives
Previously, if a directive definition object was defined with methods like `compile`
provided on the prototype rather than the instance, the Angular compiler failed
to use these methods when the directive had a `templateURL`. This change ensures
that these prototypical methods are not lost.

This enables developers to define their directives using "classes" such as
in CoffeeScript or ES6.

Closes #10926
2015-02-02 12:01:31 +00:00
Henry Zhu 286a40751c style(*): add jscs rule disallowKeywordsOnNewLine: "else" 2015-01-30 12:47:37 +00:00
Henry Zhu eca0535457 style(*): add jscs rule requireSpaceBeforeKeywords
Closes #10772
2015-01-30 12:47:37 +00:00
Henry Zhu 7ace77a5d7 style(*): add jscs rule requireSpacesInForStatement
Closes #10772
2015-01-30 12:47:36 +00:00
Henry Zhu 7f362af153 style(*): add jscs rule disallowSpacesInCallExpression
Closes #10772
2015-01-30 12:47:36 +00:00
Henry Zhu 35dee2abac style(*): add jscs rule disallowKeywordsOnNewLine: "else"
Closes #10772
2015-01-30 12:47:36 +00:00
Peter Bacon Darwin 29c926201d chore(npm): update grunt-jscs to 1.2.0 (jscs to 1.10.0), fix styles
Closes #10772
2015-01-30 12:47:36 +00:00
Peter Bacon Darwin 9b6852a8c9 chore(doc-gen): update to dgeni-packages 0.10.8
Closes https://github.com/angular/dgeni-packages/pull/105
2015-01-30 11:03:39 +00:00
Rohit Kandhal 53efc8d5d0 docs(tutorial/step-11): update link to Jasmine matchers
Closes #10909
2015-01-29 23:01:34 +00:00
Martin Staffa 0b8461c9cb test(validators): minlength and required must use viewValue in $isEmpty 2015-01-29 23:30:29 +01:00
Martin Staffa abd8e2a9eb fix(validators): maxlength should use viewValue for $isEmpty
Closes #10898
2015-01-29 23:30:28 +01:00
Ian Young bc4dadc894 docs(ngHide): use proper selector when overriding the CSS
The animation selector gives the default styles greater specificity that
should be matched when overriding.

Closes #10902
Closes #10913
2015-01-29 22:13:21 +00:00
Caitlin Potter ec53089bb1 chore($controller): don't use new for minErr instance
minErr creates a new error anyways, it's not meant to be called as a constructor.
2015-01-29 16:27:57 -05:00
Martin Staffa 7bb50e2823 docs(guide/production): clarification in disabling debug data
Closes #10762
2015-01-29 20:01:40 +01:00
Caitlin Potter 632b2ddd34 fix($controller): throw better error when controller expression is bad
Previously, the error was a JS runtime error when trying to access a property of `null`. But, it's
a bit nicer to throw a real error and provide a description of how to fix it. Developer ergonomics
and all that.

Closes #10875
Closes #10910
2015-01-29 12:52:45 -05:00
Vojta Jina 7caad2205a fix($parse): remove references to last arguments to a fn call
This can be an issue if running (and killing) multiple apps/injectors on
the same page. The `args` array holds references to all previous arguments
to a function call and thus they cannot be garbage-collected.

In a regular (one app/injector on a page) app, this is not an issue.

Closes #10894
2015-01-29 14:53:54 +00:00
Wes Alvaro 9bf5f89659 routeParams are {!Object<string, string>}
Calling Object.keys on null is a TypeError. And the mapping should be string -> string.

Closes #10896
2015-01-29 14:40:02 +00:00
Owen Smith f41ca4a53e fix(ngRoute): dont duplicate optional params into query
When calling updateParams with properties which were optional, but
previously undefined, they would be duplicated into the query params as
well as into the path.

Closes #10689
2015-01-29 10:36:03 +00:00
marc 0bcd0872d8 fix(ngScenario): Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
I know protractor is preferred, and ngScenario is only in maintenance mode. But, we are limited to
ngScenario based on the devices/browsers we are targeting (no web-driver available). So, we need
to address the bug where ngScenario does not work with manual bootstrap and also has issues if
angular.resumeBootstrap is not yet defined (race condition when lazy-loading).

Closes #10723
2015-01-29 10:15:54 +00:00
Shahar Talmi 6ec5946094 feat(ngMocks): cleanup $inject annotations after each test
this will help in detecting unannotated functions both in apps and angular core in case only part of the tests are run with strictDi
2015-01-28 14:09:41 +00:00
Agrumas 784ea8e160 chore(i18n): regenerate locales due to closure library update
This includes change to the currency symbol of Lithuania.

Closes #10855
Closes #10856
2015-01-27 10:31:37 +00:00
Peter Bacon Darwin 6ec53bdfd3 chore(i18n): update closure library to latest
This includes changed to Lithuanian currency and Mynamar Burmese date formats

Closes #10855
Closes #10856
2015-01-27 10:31:37 +00:00
Caitlin Potter d1b6480dcf docs(CHANGELOG.md): remove reverted form change 2015-01-26 18:58:27 -05:00
Jeff Cross ef6fed3ef8 revert: fix(form): ignore properties in $error prototype chain
This reverts commit adf91fe6ee.
2015-01-26 14:20:52 -08:00
Caitlin Potter 473dee5786 docs(CHANGELOG.md): add changelog notes for v1.3.11 and v1.4.0-beta.2
Closes #10876
2015-01-26 16:12:24 -05:00
Caitlin Potter 779e3f6b5f fix(htmlAnchorDirective): remove "element !== target element" check
It's not really needed due to the way click events are dispatched and propagated

Closes #10866
2015-01-25 23:42:25 -05:00
Vinti Maheshwari 71bca00651 chore(gruntFile): ensure build is run before test:modules
Closes #10188
2015-01-25 02:33:29 +00:00
Peter Bacon Darwin 9a9fce0abc test($location): ensure that link rewriting is actually being tested
If the link URL is not within the given base URL then the link would not
be rewritten anyway.

See https://github.com/angular/angular.js/pull/9906/files#r19813651
2015-01-25 00:40:57 +00:00
Peter Bacon Darwin 939ca37cfe fix($location): don't rewrite when link is shift-clicked
Closes #9904
Closes #9906
2015-01-25 00:36:44 +00:00
Peter Bacon Darwin 4ae8a2a4b6 docs(input): update example to use ngModel best practices
Update the rest of the directives to use object properties for models.

Closes #10851
2015-01-24 22:42:50 +00:00
Mark Hoffmeyer 113d3954b9 docs(input[checkbox]): update example to use ngModel best practices
It's not required for the example to function, but it prevents scope weirdness/unexpected
behavior when using directives (especially with ngTransclude!). I think it's a good pattern
to encourage and might prevent a bug down the road for for people who just scan for the
monospace font. See
[Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
for mgModel best practices.

Closes #10851
2015-01-24 22:41:55 +00:00
samilamti 7cb5983750 docs(guide/Introduction): define CRUD and add punctuation
Added description of what CRUD means.
Improved readability: Ensured that colons were followed by a capital letter
and added some sprinkled commas.

Closes #10804
2015-01-24 10:57:53 +00:00
Caitlin Potter 2b149ca6d4 fix(openPlunkr): enable cmd+click for us mac users :> 2015-01-23 16:49:00 -05:00
Mike Haggerty e77866c18c feat(openPlunkr): enable ctrl+click
This change allows users to ctrl+click on the "Edit in Plunker"
button which will set the posted form's target attribute to
"_blank" instead of "_self" which is the default.

Closes #10641
Closes #10826
2015-01-23 16:49:00 -05:00
Rouven Weßling 0dc6418d20 test($rootScope) test the correct setting of the constructor in Internet Explorer 11
Closes #10759
2015-01-23 21:33:15 +00:00
Caitlin Potter 837a077578 fix(htmlAnchorDirective): don't add event listener if replaced, ignore event if target is different element
Previously, when an `a` tag element used a directive with a replacing template, and did not include an `href` or `name` attribute
before linkage, the anchor directive would always prevent default.

Now, the anchor directive will not register an event listener at all if the original directive is replaced with a non-anchor, and
will ignore events which do not target the linked element.

Closes #4262
Closes #10849
2015-01-23 15:40:23 -05:00
Caitlin Potter adf91fe6ee fix(form): ignore properties in $error prototype chain
Closes #10469
Closes #10727
2015-01-23 14:37:48 +00:00
Boshen Chen dea1c0d34c docs(ngInit): fix code block not being displayed in the note section
Closes #10791
2015-01-20 23:23:43 +01:00
Pawel Kozlowski f533acc9aa docs(CHANGELOG): add changes for 1.3.10 and 1.4.0-beta.1 2015-01-20 20:48:20 +01:00
Alexandr Subbotin d01cae2a0d fix(ngRepeat) do not allow $id and $root as aliases
Currently user can use `$id` or `$root` as alias in ng-repeat directive that leads to rewriting
these scope-related variables. This commit fixes this behavior by throwing an error when user try
to use these values.

Closes #10778
2015-01-20 19:31:56 +01:00
Pawel Kozlowski d015c8a80b fix(ngController): allow bound constructor fns as controllers
Fixes #10784
Closes #10790
2015-01-20 19:31:47 +01:00
Alexandr Subbotin 8d2717146b refactor($templateRequest): remove repeated decrementation and unnecessary local variable
Closes #10780
2015-01-20 19:31:35 +01:00
Pawel Kozlowski 3d598dae64 docs(ngClass): fix jscs style errors 2015-01-20 19:31:23 +01:00
Evan Spiler 0a58986f52 docs(ngClass): fix formatting
Closes #10793
2015-01-20 19:31:12 +01:00
thorn0 6e69b85f9a docs(angular.bootstrap): passed fns are called on config stage
Closes #10789
2015-01-20 19:31:02 +01:00
Caitlin Potter 7a9e336028 fix($compile): support class directives on SVG elements
Closes #10736
Closes #10756
2015-01-20 19:18:33 +01:00
Matias Niemelä 9b8df52aa9 fix($animate): ensure no transitions are applied when an empty inline style object is provided
Closes #10613
Closes #10770
2015-01-19 14:38:24 +00:00
Peter Bacon Darwin 7fab29fbe1 docs(ngRepeat): document the sorting of object keys
See #6210
2015-01-19 09:18:53 +00:00
Peter Bacon Darwin 1a47fcbb8b chore(travis): update browsers to the latest version
Update the used browsers to the latest versions available

Closes #10620
2015-01-19 08:50:39 +00:00
Peter Bacon Darwin ffd4dab611 test(privateMocks): fix for the latest version of Safari 2015-01-19 08:50:39 +00:00
Martin Staffa 13edaa95c7 test(form): test if $pending inputs are correctly removed
It's a separate test because $pending behaves differently from $error
- the property is completely removed when no pending inputs / forms
are left.
2015-01-16 21:50:24 +01:00
Martin Staffa 47b1f54bba refactor(ngModel): clarify the arguments of $setValidity 2015-01-16 21:50:23 +01:00
Martin Staffa cdc7280dd3 fix(form): clean up success state of controls when they are removed
Fixes #10509
2015-01-16 21:50:23 +01:00
Martin Staffa 0bb282bc6d docs(migration): in 1.3, global controllers are disabled by default
Closes #10775
2015-01-16 21:35:21 +01:00
Sekib Omazic fdb09ef858 docs($rootScope.Scope): Simple typo
Closes 10767
2015-01-16 21:35:21 +01:00
Sekib Omazic 5e69cb2f9f docs(ngInclude): Typo fixed
Typo fixed
2015-01-16 21:35:21 +01:00
Sekib Omazic 5c2da38e3f docs(ngMessages): --typos.length
Merci~

Closes #10769
2015-01-15 15:46:58 -05:00
Marcin Wosinek 3a799df0f1 docs(design): highlight source button when focused 2015-01-15 14:01:21 +00:00
Kiran Rao 511c765a44 docs(CONTRIBUTING): add colons for consistent styling
Closes #10744
2015-01-15 13:54:53 +00:00
Martin Mouterde bf55d76d27 docs(date): fix milliseconds syntax description
There is no need to prefix 'sss' with ',' or '.'.

Closes #10680
2015-01-15 13:42:42 +00:00
anyong c9efc80cd0 docs(tutorial/0): remind users to refresh page
On line 32-34 after reverting to step-0 and starting the webserver, the
browser may have already cached the master branch of the app and the user
will see the master version in their browser. I just added a reminder to
tell them to refresh the page if this happens!

Closes #10615
2015-01-15 13:16:14 +00:00
Kok-Hou Chia fa15f2a6df docs(tutorial/7): correct typos
Closes #10587
2015-01-15 13:16:14 +00:00
Tyler Morgan c023a0bfbb docs(guide/Directives): demonstrate how to pass data from isolate to parent scope
It looks like this used to be in the Angular docs as per this thread:
https://groups.google.com/d/msg/angular/3CHdR_THaNw/AxqKwUw5t0oJ. I recently
spent some time trying to get this to work and was very frustrated by lack of
documentation.

Closes #10567
2015-01-15 13:16:14 +00:00
Olivier Giulieri b470e005e8 docs(css): fix position and size of Table of Contents "close" button
Closes #10555
2015-01-15 13:16:14 +00:00
Jonathan Gruber c959191882 docs(tutorial/step_10): Added missing semicolon
Added a missing semicolon in definition of $scope.setImage.

Closes #10752
2015-01-14 09:42:58 -05:00
Caitlin Potter e4adebd07a revert: chore(npm): Make require()-able as part of publish script
This reverts commit c5686c5271.

(We wanted to get some feedback before doin this)
2015-01-13 14:29:29 -05:00
Peter Bacon Darwin ac94f6125f docs(CHANGELOG): add changes for 1.3.9 and 1.4.0-beta.0 2015-01-13 00:58:04 +00:00
Ben Clinkinbeard c5686c5271 chore(npm): Make require()-able as part of publish script
(This has not been tested locally with browserify --- but it should work!
If it doesn't, please file a bug rather than just leaving a comment on this
commit :)

Closes #10731
2015-01-12 19:09:46 -05:00
Julie Ralph 7fdb54d12b chore(testing): bump protractor to version 1.6.0 2015-01-12 11:47:47 -08:00
Jason Bedard 869008140a fix($parse): allow use of locals in assignments Fixes #4664 2015-01-12 13:38:34 +00:00
Julie Ralph 26ee32ec79 chore(travis): split out the docs e2e tests into their own travis job
Previously, they were in the 'unit' job to save travis VMs, but this
was confusing and made it more difficult to track down errors easily.
2015-01-09 14:29:26 -08:00
Julie Ralph a06193f97b chore(travis): make browserstack unit tests allowed failures 2015-01-09 10:45:23 -08:00
Uri Goldshtein ba9dee170c docs(guide/index): add angular-easyfb with Facebook login to login libraries
Merci~

Closes #5792
2015-01-06 13:41:45 -05:00
Andrey Pushkarev d4b60ada1e fix(filterFilter): use isArray() to determine array type
In JavaScript, an array is a special type of object, therefore typeof [] returns object.
Added corresponding unit tests.

Changed condition for array type to isArray.

Closes #10621
2015-01-02 13:26:51 -05:00
Rus1 b839f73ad0 docs(ngInclude): replace <tt> with <code>
Using obsolete <tt> HTML tag may not be good for Angular examples

Closes #10594
2014-12-30 15:48:19 -05:00
Peter Bacon Darwin aee32931fd test(input): split tests into smaller files
This is complement to the previous commit.
It also refactors the input compile helpers to make it cleaner and more
consistent.
2014-12-24 23:26:34 +00:00
Peter Bacon Darwin 7ee5f46bbc refact(input): split input.js into smaller files
The input.js file is unnecessarily large, containing many directives including the
vast `ngModel`. This change moves ngModel and a few other directives into their
own files, which will make maintenance easier.
2014-12-24 13:01:03 +00:00
David Souther 2b97854bf4 feat(ngMock/$exceptionHandler): log errors when rethrowing
Now the `rethrow` mode will also record a log of the error in the same
way as the `log` mode.

Closes #10540
Closes #10564
2014-12-23 18:35:49 +00:00
David Souther 316ee8f7ca test($exceptionHandlerProvider): call inject() to run tests
In the current angular-mocksSpec, the tests for $exceptionHandlerProvider
call `module` to run tests on `$exceptionHandlerProvider.mode()`, but do
not call `inject()` to pump the module definitions.

Closes #10563
2014-12-23 18:11:21 +00:00
gokulkrishh 2e18f44fcd docs(guide/*): spelling/grammar improvements
Closes #10552
2014-12-22 10:44:54 -05:00
Caitlin Potter c85d064ecf docs($rootScope): remove erroneous closing parenthesis
Closes #10549
2014-12-22 08:34:29 -05:00
Kevin Primat 7b9b82281a docs(guide/location): add missing definite article
The sentence was missing a definite article so was unclear. Added one to clarify.

Closes #10547
2014-12-22 08:25:24 -05:00
Olivier Giulieri aab632b330 docs(guide): fix spaces
Closes #10539
2014-12-22 01:08:06 +00:00
gdi2290 4c8d8ad508 perf(ngStyleDirective): use $watchCollection
Since we are simply watching a flat object collection it is more performant
to use $watchCollection than a deepWatch...

Closes #10535
2014-12-22 00:58:45 +00:00
Dan Cancro 3a8f3dc9ea docs(guide/index): Link to starter options
Add a link to a comparison spreadsheet of alternative generators, examples,
tutorials and seeds that one can use to get started on a new Angular project.

Closes #10526
2014-12-22 00:30:30 +00:00
Robert Haritonov c139e68d99 docs(tutorial/12): fix path to jquery in bower
Closes #10504
2014-12-22 00:21:27 +00:00
107 changed files with 20051 additions and 7196 deletions
+9 -2
View File
@@ -1,6 +1,7 @@
{
"excludeFiles": ["src/ngLocale/**"],
"disallowKeywords": ["with"],
"disallowKeywordsOnNewLine": ["else"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineStrings": true,
"disallowNewlineBeforeBlockStatements": true,
@@ -11,6 +12,7 @@
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInCallExpression": true,
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
@@ -18,6 +20,11 @@
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"requireSpaceBeforeKeywords": [
"else",
"while",
"catch"
],
"disallowSpacesInsideParentheses": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
@@ -33,9 +40,9 @@
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInForStatement": true,
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"validateLineBreaks": "LF",
"validateParameterSeparator": ", "
"validateLineBreaks": "LF"
}
+1 -1
View File
@@ -4,10 +4,10 @@
// that correct the existing code base issues and make the new check pass.
{
"validateParameterSeparator": ", ", // Re-assert this rule when JSCS allows multiple spaces
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowKeywordsOnNewLine": ["else"],
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
+20 -2
View File
@@ -2,6 +2,12 @@ language: node_js
node_js:
- '0.10'
cache:
directories:
- node_modules
- bower_components
- docs/bower_components
branches:
except:
- /^g3_.*$/
@@ -9,9 +15,11 @@ branches:
env:
matrix:
- JOB=unit BROWSER_PROVIDER=saucelabs
- JOB=docs-e2e BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
- JOB=unit BROWSER_PROVIDER=browserstack
- JOB=docs-e2e BROWSER_PROVIDER=browserstack
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
global:
@@ -22,14 +30,24 @@ env:
- LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
matrix:
allow_failures:
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
- env: "JOB=docs-e2e BROWSER_PROVIDER=browserstack"
- env: "JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack"
- env: "JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack"
install:
# Check the size of caches
- du -sh ./node_modules ./bower_components/ ./docs/bower_components/ || true
# - npm config set registry http://23.251.144.68
# Disable the spinner, it looks bad on Travis
- npm config set spin false
# Log HTTP requests
- npm config set loglevel http
- time ./scripts/travis/npm-bundle-deps.sh
- time npm install
- npm install -g npm@2.5
# Instal npm dependecies and ensure that npm cache is not stale
- scripts/npm/install-dependencies.sh
before_script:
- mkdir -p $LOGS_DIR
+433
View File
@@ -1,3 +1,436 @@
<a name="1.4.0-beta.4"></a>
# 1.4.0-beta.4 overlyexplosive-poprocks (2015-02-09)
## Bug Fixes
- **$location:** prevent page reload if initial url has empty hash at the end
([a509e9aa](https://github.com/angular/angular.js/commit/a509e9aa149d0f88cc39f703d539f7ffd4cd6103),
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
- **$parse:** Initialize elements in an array from left to right
([966f6d83](https://github.com/angular/angular.js/commit/966f6d831f9469a917601f9a10604612cd7bd792))
- **ngAria:** ensure native controls fire a single click
([9d53e5a3](https://github.com/angular/angular.js/commit/9d53e5a38dd369dec82d82e13e078df3d6054c8a),
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
- **ngMock:** handle cases where injector is created before tests
([898714df](https://github.com/angular/angular.js/commit/898714df9ea38f9ef700015ced5ddea52f096b77),
[#10967](https://github.com/angular/angular.js/issues/10967))
- **sanitize:** handle newline characters inside special tags
([cc8755cd](https://github.com/angular/angular.js/commit/cc8755cda6efda0b52954388e8a8d5306e4bfbca),
[030a42e7](https://github.com/angular/angular.js/commit/030a42e79dec8a4bb73053762f7a54d797a058f6)
[#10943](https://github.com/angular/angular.js/issues/10943))
## Features
- **ng-jq:** adds the ability to force jqLite or a specific jQuery version
([09ee82d8](https://github.com/angular/angular.js/commit/09ee82d84dcbea4a6e8d85903af82dcd087a78a7))
<a name="1.3.13"></a>
# 1.3.13 meticulous-riffleshuffle (2015-02-09)
## Bug Fixes
- **$location:** prevent page reload if initial url has empty hash at the end
([4b3a590b](https://github.com/angular/angular.js/commit/4b3a590b009d7fdceda7f52e7ba0352a271b3256),
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
- **ngAria:** ensure native controls fire a single click
([69ee593f](https://github.com/angular/angular.js/commit/69ee593fd2cb5f1d7757efbe6b256e4458752fd7),
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
- **ngMock:** handle cases where injector is created before tests
([39ddef68](https://github.com/angular/angular.js/commit/39ddef682971d3b7282bf9d08f6eaf97b7f4bca4),
[#10967](https://github.com/angular/angular.js/issues/10967))
- **sanitize:** handle newline characters inside special tags
([11aedbd7](https://github.com/angular/angular.js/commit/11aedbd741ccddba060a9805adba1779391731da),
[ce49d4d6](https://github.com/angular/angular.js/commit/ce49d4d61bd02464b6c6376af8048f6eb09330a8)
[#10943](https://github.com/angular/angular.js/issues/10943))
<a name="1.4.0-beta.3"></a>
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
## Bug Fixes
- **$compile:**
- do not initialize optional '&' binding if attribute not specified
([6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
[#6404](https://github.com/angular/angular.js/issues/6404), [#9216](https://github.com/angular/angular.js/issues/9216))
- respect return value from controller constructor
([62d514b0](https://github.com/angular/angular.js/commit/62d514b06937cc7dd86e973ea11165c88343b42d))
- **$controller:** throw better error when controller expression is bad
([dda65e99](https://github.com/angular/angular.js/commit/dda65e992b72044c0fa0c8f5f33184028c0e3ad7),
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
- **$parse:**
- handle null targets at assign
([2e5a7e52](https://github.com/angular/angular.js/commit/2e5a7e52a0385575bbb55a801471b009afafeca3))
- remove references to last arguments to a fn call
([e61eae1b](https://github.com/angular/angular.js/commit/e61eae1b1f2351c51bcfe4142749a4e68a2806ff),
[#10894](https://github.com/angular/angular.js/issues/10894))
- **a:** don't reload if there is only a name attribute
([d729fcf0](https://github.com/angular/angular.js/commit/d729fcf030be1d3ef37196d36ea3bf3249ee3318),
[#6273](https://github.com/angular/angular.js/issues/6273), [#10880](https://github.com/angular/angular.js/issues/10880))
- **angular.copy:** support copying `TypedArray`s
([aa0f6449](https://github.com/angular/angular.js/commit/aa0f64496a66d2a5d1a4d033f2eb075a8b084a78),
[#10745](https://github.com/angular/angular.js/issues/10745))
- **filter:** format timezone correctly in the case that UTC timezone is used
([8c469191](https://github.com/angular/angular.js/commit/8c46919199090a05634789774124b38983430c76),
[#9359](https://github.com/angular/angular.js/issues/9359))
- **ngRoute:** dont duplicate optional params into query
([27bf2ce4](https://github.com/angular/angular.js/commit/27bf2ce40c5adfb1494d69c9d0ac9cf433834a12),
[#10689](https://github.com/angular/angular.js/issues/10689))
- **ngScenario:** allow ngScenario to handle lazy-loaded and manually bootstrapped applications
([c69caa7b](https://github.com/angular/angular.js/commit/c69caa7beee4e920f8f587eb3e943be99864a14f),
[#10723](https://github.com/angular/angular.js/issues/10723))
- **validators:** maxlength should use viewValue for $isEmpty
([bfcf9946](https://github.com/angular/angular.js/commit/bfcf9946e16d21b55dde50d4d21c71c898b10215),
[#10898](https://github.com/angular/angular.js/issues/10898))
## Features
- **$compile:** allow using bindToController as object, support both new/isolate scopes
([35498d70](https://github.com/angular/angular.js/commit/35498d7045ba9138016464a344e2c145ce5264c1),
[#10420](https://github.com/angular/angular.js/issues/10420), [#10467](https://github.com/angular/angular.js/issues/10467))
- **filter:** support conversion to timezone other than UTC
([c6d8512a](https://github.com/angular/angular.js/commit/c6d8512a1d7345516d1bd9a039d81821b9518bff),
[#10858](https://github.com/angular/angular.js/issues/10858))
- **ngMocks:** cleanup $inject annotations after each test
([0baa17a3](https://github.com/angular/angular.js/commit/0baa17a3b7ad2b242df2b277b81cebdf75b04287))
## Performance Improvements
- **$scope:** Add a property $$watchersCount to scope
([c1500ea7](https://github.com/angular/angular.js/commit/c1500ea775c4cb130088b7d5bb5fb872bda50bae))
- **$parse** new and more performant parser
([0d42426](https://github.com/angular/angular.js/commit/0d424263ead16635afb582affab2b147f8e71626))
## Breaking Changes
- **$compile:** due to [6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding
is marked as optional and the attribute is not specified, no function will be added to the isolate scope.
<a name="1.3.12"></a>
# 1.3.12 outlandish-knitting (2015-02-02)
## Bug Fixes
- **$controller:** throw better error when controller expression is bad
([632b2ddd](https://github.com/angular/angular.js/commit/632b2ddd34c07b3b5a207bd83ca3a5e6e613e63b),
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
- **$parse:** remove references to last arguments to a fn call
([7caad220](https://github.com/angular/angular.js/commit/7caad2205a6e9927890192a3638f55532bdaaf75),
[#10894](https://github.com/angular/angular.js/issues/10894))
- **ngRoute:** dont duplicate optional params into query
([f41ca4a5](https://github.com/angular/angular.js/commit/f41ca4a53ed53f172fb334911be56e42aad58794),
[#10689](https://github.com/angular/angular.js/issues/10689))
- **ngScenario:** Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
([0bcd0872](https://github.com/angular/angular.js/commit/0bcd0872d8d2e37e6cb7aa5bc5cb0c742b4294f9),
[#10723](https://github.com/angular/angular.js/issues/10723))
- **validators:** maxlength should use viewValue for $isEmpty
([abd8e2a9](https://github.com/angular/angular.js/commit/abd8e2a9eb2d21ac67989c2f7b64c4c6547a1585),
[#10898](https://github.com/angular/angular.js/issues/10898))
## Features
- **ngMocks:** cleanup $inject annotations after each test
([6ec59460](https://github.com/angular/angular.js/commit/6ec5946094ee92b820bbacc886fa2367715e60b4))
<a name="1.4.0-beta.2"></a>
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
## Bug Fixes
- **$location:** don't rewrite when link is shift-clicked
([8b33de6f](https://github.com/angular/angular.js/commit/8b33de6fd0ec0eb785fed697f062763b5c1d8d23),
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
- **$templateRequest:** cache downloaded templates as strings
([b3a9bd3a](https://github.com/angular/angular.js/commit/b3a9bd3ae043e3042ea7ccfe08e3b36a84feb35e),
[#10630](https://github.com/angular/angular.js/issues/10630), [#10646](https://github.com/angular/angular.js/issues/10646))
- **filterFilter:** throw error if input is not an array
([cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
[#9992](https://github.com/angular/angular.js/issues/9992), [#10352](https://github.com/angular/angular.js/issues/10352))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([2958cd30](https://github.com/angular/angular.js/commit/2958cd308b5ebaf223a3e5df3fb5bf0f23408447),
[#10866](https://github.com/angular/angular.js/issues/10866))
- don't add event listener if replaced, ignore event if target is different element
([b146af11](https://github.com/angular/angular.js/commit/b146af11271de8fa4c51c6db87df104269f41a33),
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
- **ngPluralize:** fix wrong text content when count is null/undefined
([3228d3b4](https://github.com/angular/angular.js/commit/3228d3b4991af681e57de5ab079c1e1c11cf35cb),
[#10836](https://github.com/angular/angular.js/issues/10836), [#10841](https://github.com/angular/angular.js/issues/10841))
## Breaking Changes
- **filterFilter:** due to [cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
Previously, the filter was not applied if used with a non array.
Now, it throws an error. This can be worked around by converting an object to an array, using
a filter such as https://github.com/petebacondarwin/angular-toArrayFilter
Closes #9992
Closes #10352
<a name="1.3.11"></a>
# 1.3.11 spiffy-manatee (2015-01-26)
## Bug Fixes
- **$location:** don't rewrite when link is shift-clicked
([939ca37c](https://github.com/angular/angular.js/commit/939ca37cfe5f6fc35b09b6705caabd1fcc3cf9d3),
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([779e3f6b](https://github.com/angular/angular.js/commit/779e3f6b5f8d2550e758cb0c5f64187ba8e00e29),
[#10866](https://github.com/angular/angular.js/issues/10866))
- don't add event listener if replaced, ignore event if target is different element
([837a0775](https://github.com/angular/angular.js/commit/837a077578081bbd07863bef85241537d19fa652),
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
<a name="1.4.0-beta.1"></a>
# 1.4.0-beta.1 trepidatious-salamander (2015-01-20)
## Bug Fixes
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
([0db5b21b](https://github.com/angular/angular.js/commit/0db5b21b1d09431535e0c0bf8ac63d4b5b24d349),
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
- **$compile:** support class directives on SVG elements
([23c8a90d](https://github.com/angular/angular.js/commit/23c8a90d22f7c7b41b5a756b89498ffac828980a),
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
- **form:** clean up success state of controls when they are removed
([2408f2de](https://github.com/angular/angular.js/commit/2408f2ded5ead6e678c241e38ef474c1fadff92b),
[#10509](https://github.com/angular/angular.js/issues/10509))
- **ngController:** allow bound constructor fns as controllers
([d17fbc38](https://github.com/angular/angular.js/commit/d17fbc3862e0a2e646db1222f184dbe663da4a1f),
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
- **ngRepeat:** do not sort object keys alphabetically
([c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
[#6210](https://github.com/angular/angular.js/issues/6210), [#10538](https://github.com/angular/angular.js/issues/10538))
## Features
- **$http:** provide a config object as an argument to header functions
([d435464c](https://github.com/angular/angular.js/commit/d435464c51d3912f56cfc830d86bfc64a1578327),
[#7235](https://github.com/angular/angular.js/issues/7235), [#10622](https://github.com/angular/angular.js/issues/10622))
## Breaking Changes
- **ngRepeat:** due to [c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
Previously, the order of items when using ngRepeat to iterate
over object properties was guaranteed to be consistent by sorting the
keys into alphabetic order.
Now, the order of the items is browser dependent based on the order returned
from iterating over the object using the `for key in obj` syntax.
It seems that browsers generally follow the strategy of providing
keys in the order in which they were defined, although there are exceptions
when keys are deleted and reinstated. See
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
The best approach is to convert Objects into Arrays by a filter such as
https://github.com/petebacondarwin/angular-toArrayFilter
or some other mechanism, and then sort them manually in the order you need.
Closes #6210
Closes #10538
<a name="1.3.10"></a>
# 1.3.10 heliotropic-sundial (2015-01-20)
## Bug Fixes
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
([9b8df52a](https://github.com/angular/angular.js/commit/9b8df52aa960b9b6288fc150d55ea2e35f56555e),
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
- **$compile:** support class directives on SVG elements
([7a9e3360](https://github.com/angular/angular.js/commit/7a9e3360284d58197a1fe34de57f5e0f6d1f4a76),
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
- **form:** clean up success state of controls when they are removed
([cdc7280d](https://github.com/angular/angular.js/commit/cdc7280dd3d5a2ded784c06dd55fe36c2053fb6f),
[#10509](https://github.com/angular/angular.js/issues/10509))
- **ngController:** allow bound constructor fns as controllers
([d015c8a8](https://github.com/angular/angular.js/commit/d015c8a80b28754633c846fc50d11c9437519486),
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
<a name="1.4.0-beta.0"></a>
# 1.4.0-beta.0 photonic-umbrakinesis (2015-01-13)
## Bug Fixes
- **$location:** support right button click on anchors in firefox
([aa798f12](https://github.com/angular/angular.js/commit/aa798f123658cb78b5581513d26577016195cafe),
[#7984](https://github.com/angular/angular.js/issues/7984))
- **$templateRequest:** propagate HTTP status on failed requests
([e24f22bd](https://github.com/angular/angular.js/commit/e24f22bdb1740388938d58778aa24d307a79a796),
[#10514](https://github.com/angular/angular.js/issues/10514), [#10628](https://github.com/angular/angular.js/issues/10628))
- **dateFilter:** ignore invalid dates
([1334b8c8](https://github.com/angular/angular.js/commit/1334b8c8326b93e0ca016c85516627900c7a9fd3),
[#10640](https://github.com/angular/angular.js/issues/10640))
- **filterFilter:** use isArray() to determine array type
([a01ce6b8](https://github.com/angular/angular.js/commit/a01ce6b81c197b0a4a1057981e8e9c1b74f37587),
[#10621](https://github.com/angular/angular.js/issues/10621))
- **ngChecked:** ensure that ngChecked doesn't interfere with ngModel
([e079111b](https://github.com/angular/angular.js/commit/e079111b33bf36be21c0941718b41cc9ca67bea0),
[#10662](https://github.com/angular/angular.js/issues/10662), [#10664](https://github.com/angular/angular.js/issues/10664))
- **ngClass:** handle multi-class definitions as an element of an array
([e1132f53](https://github.com/angular/angular.js/commit/e1132f53b03a5a71aa9b6eded24d64e3bc83929b),
[#8578](https://github.com/angular/angular.js/issues/8578), [#10651](https://github.com/angular/angular.js/issues/10651))
- **ngModelOptions:** allow sharing options between multiple inputs
([9c9c6b3f](https://github.com/angular/angular.js/commit/9c9c6b3fe4edfe78ae275c413ee3eefb81f1ebf6),
[#10667](https://github.com/angular/angular.js/issues/10667))
- **ngOptions:**
- support one-time binding on the option values
([ba90261b](https://github.com/angular/angular.js/commit/ba90261b7586b519483883800ea876510faf5c21),
[#10687](https://github.com/angular/angular.js/issues/10687), [#10694](https://github.com/angular/angular.js/issues/10694))
- prevent infinite digest if track by expression is stable
([fc21db8a](https://github.com/angular/angular.js/commit/fc21db8a15545fad53124fc941b3c911a8d57067),
[#9464](https://github.com/angular/angular.js/issues/9464))
- update model if selected option is removed
([933591d6](https://github.com/angular/angular.js/commit/933591d69cee2c5580da1d8522ba90a7d924da0e),
[#7736](https://github.com/angular/angular.js/issues/7736))
- ensure that the correct option is selected when options are loaded async
([7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
[#8019](https://github.com/angular/angular.js/issues/8019), [#9714](https://github.com/angular/angular.js/issues/9714), [#10639](https://github.com/angular/angular.js/issues/10639))
- **ngPluralize:** generate a warning when using a not defined rule
([c66b4b6a](https://github.com/angular/angular.js/commit/c66b4b6a133f7215d50c23db516986cfc1f0a985))
## Features
- **$filter:** display Infinity symbol when number is Infinity
([51d67742](https://github.com/angular/angular.js/commit/51d6774286202b55ade402ca097e417e70fd546b),
[#10421](https://github.com/angular/angular.js/issues/10421))
- **$timeout:** allow `fn` to be an optional parameter
([5a603023](https://github.com/angular/angular.js/commit/5a60302389162c6ef45f311c1aaa65a00d538c66),
[#9176](https://github.com/angular/angular.js/issues/9176))
- **limitTo:** ignore limit when invalid
([a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
[#10510](https://github.com/angular/angular.js/issues/10510))
- **ngMock/$exceptionHandler:** log errors when rethrowing
([deb3cb4d](https://github.com/angular/angular.js/commit/deb3cb4daef0054457bd9fb8995829fff0e8f1e4),
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
## Performance Improvements
- **ngStyleDirective:** use $watchCollection
([8928d023](https://github.com/angular/angular.js/commit/8928d0234551a272992d0eccef73b3ad6cb8bfd1),
[#10535](https://github.com/angular/angular.js/issues/10535))
## Breaking Changes
- **limitTo:** due to [a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
limitTo changed behavior when limit value is invalid.
Instead of returning empty object/array it returns unchanged input.
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
This commit changes the actual string used as the surrogate key. We now store a string that is computed
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
item in the collection.
(This is in keeping with the way that the unknown option value is represented in the select directive.)
Before you might have seen:
```
<select ng-model="x" ng-option="i in items">
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
<option value="4">d</option>
</select>
```
Now it will be something like:
```
<select ng-model="x" ng-option="i in items">
<option value="string:a">a</option>
<option value="string:b">b</option>
<option value="string:c">c</option>
<option value="string:d">d</option>
</select>
```
If your application code relied on this value, which it shouldn't, then you will need to modify your
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
as this provides the ability to specify the key that is stored.
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
When iterating over an object's properties using the `(key, value) in obj` syntax
the order of the elements used to be sorted alphabetically. This was an artificial
attempt to create a deterministic ordering since browsers don't guarantee the order.
But in practice this is not what people want and so this change iterates over properties
in the order they are returned by Object.keys(obj), which is almost always the order
in which the properties were defined.
<a name="1.3.9"></a>
# 1.3.9 multidimensional-awareness (2015-01-13)
## Bug Fixes
- **$parse:** allow use of locals in assignments
([86900814](https://github.com/angular/angular.js/commit/869008140a96e0e9e0d9774cc2e5fdd66ada7ba9))
- **filterFilter:** use isArray() to determine array type
([d4b60ada](https://github.com/angular/angular.js/commit/d4b60ada1ecff5afdb3210caa44e149e9f3d4c1b),
[#10621](https://github.com/angular/angular.js/issues/10621))
## Features
- **ngMock/$exceptionHandler:** log errors when rethrowing
([2b97854b](https://github.com/angular/angular.js/commit/2b97854bf4786fe8579974e2b9d6b4adee8a3dc3),
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
## Performance Improvements
- **ngStyleDirective:** use $watchCollection
([4c8d8ad5](https://github.com/angular/angular.js/commit/4c8d8ad5083d9dd17c0b8480339d5f95943f1b71),
[#10535](https://github.com/angular/angular.js/issues/10535))
<a name="1.3.8"></a>
# 1.3.8 prophetic-narwhal (2014-12-19)
+3 -3
View File
@@ -87,7 +87,7 @@ Before you submit your pull request consider the following guidelines:
that relates to your submission. You don't want to duplicate effort.
* Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull
requests. We cannot accept code without this.
* Make your changes in a new git branch
* Make your changes in a new git branch:
```shell
git checkout -b my-fix-branch master
@@ -107,7 +107,7 @@ Before you submit your pull request consider the following guidelines:
```
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
* Build your changes locally to ensure all the tests pass
* Build your changes locally to ensure all the tests pass:
```shell
grunt test
@@ -120,7 +120,7 @@ Before you submit your pull request consider the following guidelines:
```
* In GitHub, send a pull request to `angular:master`.
* If we suggest changes then
* If we suggest changes then:
* Make the required updates.
* Re-run the Angular test suite to ensure tests are still passing.
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
+10 -6
View File
@@ -17,10 +17,6 @@ module.exports = function(grunt) {
NG_VERSION.cdn = versionInfo.cdnVersion;
var dist = 'angular-'+ NG_VERSION.full;
//global beforeEach
util.init();
//config
grunt.initConfig({
NG_VERSION: NG_VERSION,
@@ -285,6 +281,10 @@ module.exports = function(grunt) {
},
shell: {
"npm-install": {
command: path.normalize('scripts/npm/install-dependencies.sh')
},
"promises-aplus-tests": {
options: {
stdout: false,
@@ -311,14 +311,18 @@ module.exports = function(grunt) {
}
});
// global beforeEach task
if (!process.env.TRAVIS) {
grunt.task.run('shell:npm-install');
}
//alias tasks
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', '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:modules', 'Run the Karma module tests with Karma', ['build', '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:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:modules']);
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:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
+5 -1
View File
@@ -53,6 +53,7 @@ var angularFiles = {
'src/ng/directive/form.js',
'src/ng/directive/input.js',
'src/ng/directive/ngBind.js',
'src/ng/directive/ngChange.js',
'src/ng/directive/ngClass.js',
'src/ng/directive/ngCloak.js',
'src/ng/directive/ngController.js',
@@ -61,6 +62,8 @@ var angularFiles = {
'src/ng/directive/ngIf.js',
'src/ng/directive/ngInclude.js',
'src/ng/directive/ngInit.js',
'src/ng/directive/ngList.js',
'src/ng/directive/ngModel.js',
'src/ng/directive/ngNonBindable.js',
'src/ng/directive/ngPluralize.js',
'src/ng/directive/ngRepeat.js',
@@ -70,7 +73,8 @@ var angularFiles = {
'src/ng/directive/ngTransclude.js',
'src/ng/directive/script.js',
'src/ng/directive/select.js',
'src/ng/directive/style.js'
'src/ng/directive/style.js',
'src/ng/directive/validators.js'
],
'angularLoader': [
+6 -5
View File
@@ -546,10 +546,10 @@ h4 {
margin-left:10px;
}
.btn:hover {
color:black!important;
.btn:hover, .btn:focus {
color: black!important;
border: 1px solid #ddd!important;
background:white!important;
background: white!important;
}
.view-source, .improve-docs {
@@ -631,6 +631,7 @@ ul.events > li {
}
.main-body-grid .side-navigation {
display:block!important;
padding-bottom:50px;
}
.main-body-grid .side-navigation.ng-hide {
display:none!important;
@@ -656,14 +657,14 @@ ul.events > li {
}
.toc-close {
position: absolute;
bottom: -50px;
bottom: 5px;
left: 50%;
margin-left: -50%;
text-align: center;
padding: 5px;
background: #eee;
border-radius: 5px;
width: 90%;
width: 100%;
border:1px solid #ddd;
box-shadow:0 0 10px #bbb;
}
+11 -7
View File
@@ -1,13 +1,16 @@
angular.module('examples', [])
.factory('formPostData', ['$document', function($document) {
return function(url, fields) {
return function(url, newWindow, fields) {
/**
* Form previously posted to target="_blank", but pop-up blockers were causing this to not work.
* If a user chose to bypass pop-up blocker one time and click the link, they would arrive at
* a new default plnkr, not a plnkr with the desired template.
* If the form posts to target="_blank", pop-up blockers can cause it not to work.
* If a user choses to bypass pop-up blocker one time and click the link, they will arrive at
* a new default plnkr, not a plnkr with the desired template. Given this undesired behavior,
* some may still want to open the plnk in a new window by opting-in via ctrl+click. The
* newWindow param allows for this possibility.
*/
var form = angular.element('<form style="display: none;" method="post" action="' + url + '"></form>');
var target = newWindow ? '_blank' : '_self';
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="' + target + '"></form>');
angular.forEach(fields, function(value, name) {
var input = angular.element('<input type="hidden" name="' + name + '">');
input.attr('value', value);
@@ -21,9 +24,10 @@ angular.module('examples', [])
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
return function(exampleFolder) {
return function(exampleFolder, clickEvent) {
var exampleName = 'AngularJS Example';
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
// Load the manifest for the example
$http.get(exampleFolder + '/manifest.json')
@@ -71,7 +75,7 @@ angular.module('examples', [])
postData.private = true;
postData.description = exampleName;
formPostData('http://plnkr.co/edit/?p=preview', postData);
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
});
};
}]);
@@ -2,7 +2,7 @@
is HTML and wrap each line in a <p> - thus breaking the HTML #}
<div>
<a ng-click="openPlunkr('{$ doc.path $}')" class="btn pull-right">
<a ng-click="openPlunkr('{$ doc.path $}', $event)" class="btn pull-right">
<i class="glyphicon glyphicon-edit">&nbsp;</i>
Edit in Plunker</a>
@@ -0,0 +1,46 @@
@ngdoc error
@name $controller:ctrlfmt
@fullName Badly formed controller string
@description
This error occurs when {@link ng.$controller $controller} service is called
with a string that does not match the supported controller string formats.
Supported formats:
1. `__name__`
2. `__name__ as __identifier__`
N'either `__name__` or `__identifier__` may contain spaces.
Example of incorrect usage that leads to this error:
```html
<!-- unclosed ng-controller attribute messes up the format -->
<div ng-controller="myController>
```
or
```js
// does not match `__name__` or `__name__ as __identifier__`
var myCtrl = $controller("mY contRoller", { $scope: newScope });
```
or
```js
directive("myDirective", function() {
return {
// does not match `__name__` or `__name__ as __identifier__`
controller: "mY contRoller",
link: function() {}
};
});
```
To fix the examples above, ensure that the controller string matches the supported
formats, and that any html attributes which are used as controller expressions are
closed.
Please consult the {@link ng.$controller $controller} service api docs to learn more.
+1 -1
View File
@@ -35,7 +35,7 @@ var users = [ { name: 'Hank' }, { name: 'Francisco' } ];
$scope.getUsers = function() {
return users;
});
};
```
The maximum number of allowed iterations of the `$digest` cycle is controlled via TTL setting which can be configured via {@link ng.$rootScopeProvider $rootScopeProvider}.
@@ -16,6 +16,8 @@ Reserved names include:
- `this`
- `undefined`
- `$parent`
- `$id`
- `$root`
- `$even`
- `$odd`
- `$first`
+1 -1
View File
@@ -358,7 +358,7 @@ legacy browsers and hashbang links in modern browser:
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
that you can see the differences. These `$location` services are connected to a fake browsers. Each
input represents address bar of the browser.
input represents the address bar of the browser.
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
+9 -5
View File
@@ -37,6 +37,7 @@ Currently, ngAria interfaces with the following directives:
* {@link guide/accessibility#nghide ngHide}
* {@link guide/accessibility#ngclick ngClick}
* {@link guide/accessibility#ngdblclick ngDblClick}
* {@link guide/accessibility#ngmessages ngMessages}
<h2 id="ngmodel">ngModel</h2>
@@ -209,10 +210,14 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
should also bind `ng-keypress`.
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
already.
For `ng-click`, keypress will also be bound to `div` and `li` elements. You can turn this
functionality on or off with the `bindKeypress` configuration option.
For `ng-dblclick`, you must manually add `ng-keypress` to non-interactive elements such as `div`
or `taco-button` to enable keyboard access.
<h3>Example</h3>
```html
@@ -223,7 +228,6 @@ Becomes:
```html
<div ng-click="toggleMenu()" tabindex="0"></div>
```
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
<h2 id="ngmessages">ngMessages</h2>
+2 -2
View File
@@ -88,7 +88,7 @@ As a best practice, consider adding an `ng-strict-di` directive on the same elem
```
This will ensure that all services in your application are properly annotated.
See the {@link guide/di#using-strict-dependency-injection dependancy injection strict mode} docs
See the {@link guide/di#using-strict-dependency-injection dependency injection strict mode} docs
for more.
@@ -156,4 +156,4 @@ until `angular.resumeBootstrap()` is called.
`angular.resumeBootstrap()` takes an optional array of modules that
should be added to the original list of modules that the app was
about to be bootstrapped with.
about to be bootstrapped with.
+14 -4
View File
@@ -751,9 +751,12 @@ own behavior to it.
angular.module('docsIsoFnBindExample', [])
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
$scope.name = 'Tobias';
$scope.hideDialog = function () {
$scope.message = '';
$scope.hideDialog = function (message) {
$scope.message = message;
$scope.dialogIsHidden = true;
$timeout(function () {
$scope.message = '';
$scope.dialogIsHidden = false;
}, 2000);
};
@@ -771,14 +774,15 @@ own behavior to it.
</file>
<file name="index.html">
<div ng-controller="Controller">
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
{{message}}
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
Check out the contents, {{name}}!
</my-dialog>
</div>
</file>
<file name="my-dialog-close.html">
<div class="alert">
<a href class="close" ng-click="close()">&times;</a>
<a href class="close" ng-click="close({message: 'closing for now'})">&times;</a>
<div ng-transclude></div>
</div>
</file>
@@ -795,9 +799,15 @@ callback functions to directive behaviors.
When the user clicks the `x` in the dialog, the directive's `close` function is called, thanks to
`ng-click.` This call to `close` on the isolated scope actually evaluates the expression
`hideDialog()` in the context of the original scope, thus running `Controller`'s `hideDialog`
`hideDialog(message)` in the context of the original scope, thus running `Controller`'s `hideDialog`
function.
Often it's desirable to pass data from the isolate scope via an expression to the
parent scope, this can be done by passing a map of local variable names and values into the expression
wrapper fn. For example, the hideDialog function takes a message to display when the dialog is hidden.
This is specified in the directive by calling `close({message: 'closing for now'})`. Then the local
variable `message` will be available within the `on-close` expression.
<div class="alert alert-success">
**Best Practice:** use `&attr` in the `scope` option when you want your directive
to expose an API for binding to behaviors.
+8 -7
View File
@@ -6,11 +6,12 @@
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
A Form is a collection of controls for the purpose of grouping related controls together.
Form and controls provide validation services, so that the user can be notified of invalid input.
This provides a better user experience, because the user gets instant feedback on how to
correct the error. Keep in mind that while client-side validation plays an important role
in providing good user experience, it can easily be circumvented and thus can not be trusted.
Server-side validation is still necessary for a secure application.
Form and controls provide validation services, so that the user can be notified of invalid input
before submitting a form. This provides a better user experience than server-side validation alone
because the user gets instant feedback on how to correct the error. Keep in mind that while
client-side validation plays an important role in providing good user experience, it can easily
be circumvented and thus can not be trusted. Server-side validation is still necessary for a
secure application.
# Simple form
@@ -131,7 +132,7 @@ A form is an instance of {@link form.FormController FormController}.
The form instance can optionally be published into the scope using the `name` attribute.
Similarly, an input control that has the {@link ng.directive:ngModel ngModel} directive holds an
instance of {@link ngModel.NgModelController NgModelController}.Such a control instance
instance of {@link ngModel.NgModelController NgModelController}. Such a control instance
can be published as a property of the form instance using the `name` attribute on the input control.
The name attribute specifies the name of the property on the form instance.
@@ -339,7 +340,7 @@ In the following example we create two directives:
<div>
Username:
<input type="text" ng-model="name" name="name" username />{{name}}<br />
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
<span ng-show="form.name.$pending.username">Checking if this name is available...</span>
<span ng-show="form.name.$error.username">This username is already taken!</span>
</div>
+3 -2
View File
@@ -53,7 +53,7 @@ In Angular applications, you move the job of filling page templates with data fr
## Specific Topics
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Faceb0ok library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
@@ -62,6 +62,7 @@ In Angular applications, you move the job of filling page templates with data fr
## Tools
* **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/)
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
@@ -101,7 +102,7 @@ This is a short list of libraries with specific support and documentation for wo
## Learning Resources
###Books
* [AngularJS](http://www.amazon.com/AngularJS-Brad-Green/dp/1449344852) by Brad Green and Shyam Seshadri
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
+12 -12
View File
@@ -12,7 +12,7 @@ succinctly. Angular's data binding and dependency injection eliminate much of th
would otherwise have to write. And it all happens within the browser, making it
an ideal partner with any server technology.
Angular is what HTML would have been had it been designed for applications. HTML is a great
Angular is what HTML would have been, had it been designed for applications. HTML is a great
declarative language for static documents. It does not contain much in the way of creating
applications, and as a result building web applications is an exercise in *what do I have to do
to trick the browser into doing what I want?*
@@ -28,10 +28,10 @@ The impedance mismatch between dynamic applications and static documents is ofte
Angular takes another approach. It attempts to minimize the impedance mismatch between document
centric HTML and what an application needs by creating new HTML constructs. Angular teaches the
browser new syntax through a construct we call directives. Examples include:
browser new syntax through a construct we call *directives*. Examples include:
* Data binding, as in `{{}}`.
* DOM control structures for repeating/hiding DOM fragments.
* DOM control structures for repeating, showing and hiding DOM fragments.
* Support for forms and form validation.
* Attaching new behavior to DOM elements, such as DOM event handling.
* Grouping of HTML into reusable components.
@@ -42,20 +42,20 @@ browser new syntax through a construct we call directives. Examples include:
Angular is not a single piece in the overall puzzle of building the client-side of a web
application. It handles all of the DOM and AJAX glue code you once wrote by hand and puts it in a
well-defined structure. This makes Angular opinionated about how a CRUD application should be
built. But while it is opinionated, it also tries to make sure that its opinion is just a
starting point you can easily change. Angular comes with the following out-of-the-box:
well-defined structure. This makes Angular opinionated about how a CRUD (Create, Read, Update, Delete)
application should be built. But while it is opinionated, it also tries to make sure that its opinion
is just a starting point you can easily change. Angular comes with the following out-of-the-box:
* Everything you need to build a CRUD app in a cohesive set: data-binding, basic templating
directives, form validation, routing, deep-linking, reusable components, dependency injection.
* Testability story: unit-testing, end-to-end testing, mocks, test harnesses.
* Everything you need to build a CRUD app in a cohesive set: Data-binding, basic templating
directives, form validation, routing, deep-linking, reusable components and dependency injection.
* Testability story: Unit-testing, end-to-end testing, mocks and test harnesses.
* Seed application with directory layout and test scripts as a starting point.
## Angular Sweet Spot
## Angular's sweet spot
Angular simplifies application development by presenting a higher level of abstraction to the
developer. Like any abstraction, it comes at a cost of flexibility. In other words not every app
developer. Like any abstraction, it comes at a cost of flexibility. In other words, not every app
is a good fit for Angular. Angular was built with the CRUD application in mind. Luckily CRUD
applications represent the majority of web applications. To understand what Angular is
good at, though, it helps to understand when an app is not a good fit for Angular.
@@ -78,7 +78,7 @@ expressing business logic.
* It is an excellent idea to decouple the client side of an app from the server side. This
allows development work to progress in parallel, and allows for reuse of both sides.
* It is very helpful indeed if the framework guides developers through the entire journey of
building an app: from designing the UI, through writing the business logic, to testing.
building an app: From designing the UI, through writing the business logic, to testing.
* It is always good to make common tasks trivial and difficult tasks possible.
+43 -5
View File
@@ -15,13 +15,51 @@ which drives many of these changes.
# Migrating from 1.2 to 1.3
## Controllers
Due to [3f2232b5](https://github.com/angular/angular.js/commit/3f2232b5a181512fac23775b1df4a6ebda67d018),
`$controller` will no longer look for controllers on `window`.
The old behavior of looking on `window` for controllers was originally intended
for use in examples, demos, and toy apps. We found that allowing global controller
functions encouraged poor practices, so we resolved to disable this behavior by
default.
To migrate, register your controllers with modules rather than exposing them
as globals:
Before:
```javascript
function MyController() {
// ...
}
```
After:
```javascript
angular.module('myApp', []).controller('MyController', [function() {
// ...
}]);
```
Although it's not recommended, you can re-enable the old behavior like this:
```javascript
angular.module('myModule').config(['$controllerProvider', function($controllerProvider) {
// this option might be handy for migrating old apps, but please don't use it
// in new ones!
$controllerProvider.allowGlobals();
}]);
```
## Angular Expression Parsing (`$parse` + `$interpolate`)
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
This is to disallow changing the behaviour of existing functions
in an unforseen fashion.
in an unforeseen fashion.
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
@@ -877,7 +915,7 @@ of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanit
module is not loaded) and the bound expression evaluates to a value that is not trusted an
exception is thrown.
When using this directive you can either include `ngSanitize` in your module's dependencis (See the
When using this directive you can either include `ngSanitize` in your module's dependencies (See the
example at the {@link ngBindHtml} reference) or use the {@link $sce} service to set the value as
trusted.
@@ -1134,10 +1172,10 @@ freely available to JavaScript code (as before).
Angular expressions execute in a limited context. They do not have
direct access to the global scope, `window`, `document` or the Function
constructor. However, they have direct access to names/properties on
the scope chain. It has been a long standing best practice to keep
constructor. However, they have direct access to names/properties on
the scope chain. It has been a long standing best practice to keep
sensitive APIs outside of the scope chain (in a closure or your
controller.) That's easier said that done for two reasons:
controller.) That's easier said than done for two reasons:
1. JavaScript does not have a notion of private properties so if you need
someone on the scope chain for JavaScript use, you also expose it to
+5 -5
View File
@@ -10,13 +10,13 @@ There are a few things you might consider when running your AngularJS applicatio
## Disabling Debug Data
By default AngularJS attaches information about scopes to DOM nodes, and adds CSS classes
to data-bound elements. The information that is not included is:
By default AngularJS attaches information about binding and scopes to DOM nodes,
and adds CSS classes to data-bound elements:
As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-class` is attached to the corresponding element.
- As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-binding` are attached to the corresponding element.
Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
- Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
CSS class are attached to the corresponding element. These scope references can then be accessed via
`element.scope()` and `element.isolateScope()`.
+1 -1
View File
@@ -67,7 +67,7 @@ illustration we typically build snappy apps with hundreds or thousands of active
### How big is the angular.js file that I need to include?
The size of the file is < 36KB compressed and minified.
The size of the file is ~50KB compressed and minified.
### Can I use the open-source Closure Library with Angular?
+3
View File
@@ -33,6 +33,9 @@ To see the app running in a browser, open a *separate* terminal/command line tab
run `npm start` to start the web server. Now, open a browser window for the app and navigate to
<a href="http://localhost:8000/app/" target="_blank">`http://localhost:8000/app/`</a>
Note that if you already ran the master branch app prior to checking out step-0, you may see the cached
master version of the app in your browser window at this point. Just hit refresh to re-load the page.
You can now see the page in your browser. It's not very exciting, but that's OK.
The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
+6 -6
View File
@@ -21,7 +21,7 @@ multiple views by adding routing, using an Angular module called 'ngRoute'.
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
is distributed separately from the core Angular framework.
We are using [Bower][bower] to install client side dependencies. This step updates the
We are using [Bower][bower] to install client-side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
```json
@@ -46,7 +46,7 @@ The new dependency `"angular-route": "~1.3.0"` tells bower to install a version
angular-route component that is compatible with version 1.3.x. We must tell bower to download
and install this dependency.
If you have bower installed globally then you can run `bower install` but for this project we have
If you have bower installed globally, then you can run `bower install` but for this project, we have
preconfigured npm to run bower install for us:
```
@@ -70,7 +70,7 @@ the current "route" — the view that is currently displayed to the user.
Application routes in Angular are declared via the {@link ngRoute.$routeProvider $routeProvider},
which is the provider of the {@link ngRoute.$route $route service}. This service makes it easy to
wire together controllers, view templates, and the current URL location in the browser. Using this
feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
feature, we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
utilize the browser's history (back and forward navigation) and bookmarks.
@@ -81,7 +81,7 @@ AngularJS, so it's important for you to understand a thing or two about how it w
When the application bootstraps, Angular creates an injector that will be used to find and inject all
of the services that are required by your app. The injector itself doesn't know anything about what
`$http` or `$route` services do, in fact it doesn't even know about the existence of these services
`$http` or `$route` services do. In fact, the injector doesn't even know about the existence of these services
unless it is configured with proper module definitions.
The injector only carries out the following steps :
@@ -295,7 +295,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
Again, note that we created a new module called `phonecatControllers`. For small AngularJS
applications, it's common to create just one module for all of your controllers if there are just a
few. As your application grows it is quite common to refactor your code into additional modules.
few. As your application grows, it is quite common to refactor your code into additional modules.
For larger apps, you will probably want to create separate modules for each major feature of
your app.
@@ -349,7 +349,7 @@ the same binding into the `phone-list.html` template, the binding will work as e
<div style="display: none">
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
`PhoneListCtrl`, let's shadow it with `this.hero = 'Batman'`. In `PhoneDetailCtrl`, we'll use
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
inheritance and model property shadowing do some wonders.
+1 -1
View File
@@ -31,7 +31,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$h
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
};
}]);
```
+1 -1
View File
@@ -288,5 +288,5 @@ learn how to improve this application with animations.
<ul doc-tutorial-nav="11"></ul>
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
[jasmine-matchers]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
[bower]: http://bower.io/
+1 -1
View File
@@ -97,7 +97,7 @@ __`app/index.html`.__
...
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/jquery/dist/jquery.js"></script>
...
+7 -11
View File
@@ -2912,16 +2912,12 @@ goog.i18n.DateTimeSymbols_my = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', '', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -2945,7 +2941,7 @@ goog.i18n.DateTimeSymbols_my = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
FIRSTDAYOFWEEK: 6,
+7 -11
View File
@@ -14493,16 +14493,12 @@ goog.i18n.DateTimeSymbols_my_MM = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', '', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -14526,7 +14522,7 @@ goog.i18n.DateTimeSymbols_my_MM = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
FIRSTDAYOFWEEK: 6,
+1 -1
View File
@@ -2112,7 +2112,7 @@ goog.i18n.NumberFormatSymbols_lt = {
SCIENTIFIC_PATTERN: '#E0',
PERCENT_PATTERN: '#,##0\u00A0%',
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
DEF_CURRENCY_CODE: 'LTL'
DEF_CURRENCY_CODE: 'EUR'
};
+6 -6
View File
@@ -35,18 +35,18 @@ module.exports = function(config, specificOptions) {
'SL_Chrome': {
base: 'SauceLabs',
browserName: 'chrome',
version: '34'
version: '39'
},
'SL_Firefox': {
base: 'SauceLabs',
browserName: 'firefox',
version: '26'
version: '31'
},
'SL_Safari': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
platform: 'OS X 10.10',
version: '8'
},
'SL_IE_9': {
base: 'SauceLabs',
@@ -71,13 +71,13 @@ module.exports = function(config, specificOptions) {
base: 'BrowserStack',
browser: 'chrome',
os: 'OS X',
os_version: 'Mountain Lion'
os_version: 'Yosemite'
},
'BS_Safari': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mountain Lion'
os_version: 'Yosemite'
},
'BS_Firefox': {
base: 'BrowserStack',
-7
View File
@@ -14,13 +14,6 @@ var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP
module.exports = {
init: function() {
if (!process.env.TRAVIS) {
shell.exec('npm install');
}
},
startKarma: function(config, singleRun, done){
var browsers = grunt.option('browsers');
var reporters = grunt.option('reporters');
+2 -2
View File
@@ -12,9 +12,9 @@ set -e
# before_script:
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3-linux.tar.gz"
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3.6-linux.tar.gz"
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
CONNECT_DOWNLOAD="sc-4.3-linux.tar.gz"
CONNECT_DOWNLOAD="sc-4.3.6-linux.tar.gz"
CONNECT_LOG="$LOGS_DIR/sauce-connect"
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
File diff suppressed because it is too large Load Diff
+6103 -1501
View File
File diff suppressed because it is too large Load Diff
+10 -5
View File
@@ -5,12 +5,18 @@
"type": "git",
"url": "https://github.com/angular/angular.js.git"
},
"engines": {
"node": "~0.10",
"npm": "~2.5"
},
"engineStrict": true,
"devDependencies": {
"angular-benchpress": "0.x.x",
"benchmark": "1.x.x",
"bower": "~1.3.9",
"browserstacktunnel-wrapper": "~1.3.1",
"canonical-path": "0.0.2",
"cheerio": "^0.17.0",
"dgeni": "^0.4.0",
"dgeni-packages": "^0.10.0",
"event-stream": "~3.1.0",
@@ -23,7 +29,7 @@
"grunt-contrib-jshint": "~0.10.0",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs": "~0.7.1",
"grunt-jscs": "~1.2.0",
"grunt-merge-conflict": "~0.0.1",
"grunt-shell": "~1.1.1",
"gulp": "~3.8.0",
@@ -37,7 +43,7 @@
"jasmine-node": "~1.14.5",
"jasmine-reporters": "~1.0.1",
"jshint-stylish": "~1.0.0",
"karma": "vojtajina/karma#socketio_10",
"karma": "~0.12.0",
"karma-browserstack-launcher": "0.1.2",
"karma-chrome-launcher": "0.1.5",
"karma-firefox-launcher": "0.1.3",
@@ -51,7 +57,7 @@
"marked": "~0.3.0",
"node-html-encoder": "0.0.2",
"promises-aplus-tests": "~2.1.0",
"protractor": "1.4.0",
"protractor": "^1.6.0",
"q": "~1.0.0",
"q-io": "^1.10.9",
"qq": "^0.3.5",
@@ -59,8 +65,7 @@
"semver": "~4.0.3",
"shelljs": "~0.3.0",
"sorted-object": "^1.0.0",
"stringmap": "^0.2.2",
"cheerio": "^0.17.0"
"stringmap": "^0.2.2"
},
"licenses": [
{
+15
View File
@@ -63,6 +63,21 @@ function prepare {
cp $BUILD_DIR/angular-csp.css $TMP_DIR/bower-angular
#
# Run local precommit script if there is one
#
for repo in "${REPOS[@]}"
do
if [ -f $TMP_DIR/bower-$repo/precommit.sh ]
then
echo "-- Running precommit.sh script for bower-$repo"
cd $TMP_DIR/bower-$repo
$TMP_DIR/bower-$repo/precommit.sh
cd $SCRIPT_DIR
fi
done
#
# update bower.json
# tag each repo
@@ -10,22 +10,17 @@
var _ = require('lodash');
var sorted = require('sorted-object');
var fs = require('fs');
var path = require('path');
function cleanModule(module, name) {
// keep `from` and `resolve` properties for git dependencies, delete otherwise
if (!(module.resolved && module.resolved.match(/^git:\/\//))) {
delete module.from;
// keep `resolve` properties for git dependencies, delete otherwise
delete module.from;
if (!(module.resolved && module.resolved.match(/^git(\+[a-z]+)?:\/\//))) {
delete module.resolved;
}
if (name === 'chokidar') {
if (module.version === '0.8.1') {
delete module.dependencies;
}
}
_.forEach(module.dependencies, function(mod, name) {
cleanModule(mod, name);
});
@@ -33,10 +28,11 @@ function cleanModule(module, name) {
console.log('Reading npm-shrinkwrap.json');
var shrinkwrap = require('./../npm-shrinkwrap.json');
var shrinkwrap = require('../../npm-shrinkwrap.json');
console.log('Cleaning shrinkwrap object');
cleanModule(shrinkwrap, shrinkwrap.name);
console.log('Writing cleaned npm-shrinkwrap.json');
fs.writeFileSync('./npm-shrinkwrap.json', JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
var cleanShrinkwrapPath = path.join(__dirname, '..', '..', 'npm-shrinkwrap.clean.json');
console.log('Writing cleaned to', cleanShrinkwrapPath);
fs.writeFileSync(cleanShrinkwrapPath, JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
set -e
SHRINKWRAP_FILE=npm-shrinkwrap.json
SHRINKWRAP_CACHED_FILE=node_modules/npm-shrinkwrap.cached.json
if diff -q $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE; then
echo 'No shrinkwrap changes detected. npm install will be skipped...';
else
echo 'Blowing away node_modules and reinstalling npm dependencies...'
rm -rf node_modules
npm install
cp $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE
echo 'npm install successful!'
fi
+1
View File
@@ -16,6 +16,7 @@ if [ $JOB = "unit" ]; then
grunt test:unit --browsers $BROWSERS --reporters dots
grunt ci-checks
grunt tests:docs --browsers $BROWSERS --reporters dots
elif [ $JOB = "docs-e2e" ]; then
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
elif [ $JOB = "e2e" ]; then
if [ $TEST_TARGET = "jquery" ]; then
+8 -5
View File
@@ -317,8 +317,7 @@ function nextUid() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
}
else {
} else {
delete obj.$$hashKey;
}
}
@@ -627,7 +626,7 @@ function isElement(node) {
function makeMap(str) {
var obj = {}, items = str.split(","), i;
for (i = 0; i < items.length; i++)
obj[ items[i] ] = true;
obj[items[i]] = true;
return obj;
}
@@ -1338,7 +1337,7 @@ function angularInit(element, bootstrap) {
* @param {DOMElement} element DOM element which is the root of angular application.
* @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
* Each item in the array should be the name of a predefined module or a (DI annotated)
* function that will be invoked by the injector as a run block.
* function that will be invoked by the injector as a `config` block.
* See: {@link angular.module modules}
* @param {Object=} config an object for defining configuration options for the application. The
* following keys are supported:
@@ -1408,8 +1407,12 @@ function bootstrap(element, modules, config) {
forEach(extraModules, function(module) {
modules.push(module);
});
doBootstrap();
return doBootstrap();
};
if (isFunction(angular.resumeDeferredBootstrap)) {
angular.resumeDeferredBootstrap();
}
}
/**
+3 -3
View File
@@ -800,7 +800,7 @@ function createInjector(modulesToLoad, strictDi) {
}
var args = [],
$inject = annotate(fn, strictDi, serviceName),
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
@@ -829,7 +829,7 @@ function createInjector(modulesToLoad, strictDi) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
@@ -839,7 +839,7 @@ function createInjector(modulesToLoad, strictDi) {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
+5 -2
View File
@@ -1450,6 +1450,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// use class as directive
className = node.className;
if (isObject(className)) {
// Maybe SVGAnimatedString
className = className.animVal;
}
if (isString(className) && className !== '') {
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
nName = directiveNormalize(match[2]);
@@ -2151,8 +2155,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
afterTemplateChildLinkFn,
beforeTemplateCompileNode = $compileNode[0],
origAsyncDirective = directives.shift(),
// The fact that we have to copy and patch the directive seems wrong!
derivedSyncDirective = extend({}, origAsyncDirective, {
derivedSyncDirective = inherit(origAsyncDirective, {
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
+9 -2
View File
@@ -1,5 +1,7 @@
'use strict';
var $controllerMinErr = minErr('$controller');
/**
* @ngdoc provider
* @name $controllerProvider
@@ -87,7 +89,12 @@ function $ControllerProvider() {
}
if (isString(expression)) {
match = expression.match(CNTRL_REG),
match = expression.match(CNTRL_REG);
if (!match) {
throw $controllerMinErr('ctrlfmt',
"Badly formed controller string '{0}'. " +
"Must match `__name__ as __id__` or `__name__`.", expression);
}
constructor = match[1],
identifier = identifier || match[3];
expression = controllers.hasOwnProperty(constructor)
@@ -111,7 +118,7 @@ function $ControllerProvider() {
// Object creation: http://jsperf.com/create-constructor/2
var controllerPrototype = (isArray(expression) ?
expression[expression.length - 1] : expression).prototype;
instance = Object.create(controllerPrototype);
instance = Object.create(controllerPrototype || null);
if (identifier) {
addIdentifier(locals, identifier, instance, constructor || expression.name);
+3
View File
@@ -18,6 +18,9 @@ var htmlAnchorDirective = valueFn({
compile: function(element, attr) {
if (!attr.href && !attr.xlinkHref && !attr.name) {
return function(scope, element) {
// If the linked element is not an anchor tag anymore, do nothing
if (element[0].nodeName.toLowerCase() !== 'a') return;
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
'xlink:href' : 'href';
+12 -9
View File
@@ -159,20 +159,23 @@
*
* @description
*
* We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
* This directive sets the `disabled` attribute on the element if the
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
*
* A special directive is necessary because we cannot use interpolation inside the `disabled`
* attribute. The following example would make the button enabled on Chrome/Firefox
* but not on older IEs:
*
* ```html
* <div ng-init="scope = { isDisabled: false }">
* <button disabled="{{scope.isDisabled}}">Disabled</button>
* <div ng-init="isDisabled = false">
* <button disabled="{{isDisabled}}">Disabled</button>
* </div>
* ```
*
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as disabled. (Their presence means true and their absence means false.)
* This is because the HTML specification does not require browsers to preserve the values of
* boolean attributes such as `disabled` (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngDisabled` directive solves this problem for the `disabled` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* @example
<example>
@@ -191,7 +194,7 @@
*
* @element INPUT
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
* then special attribute "disabled" will be set on the element
* then the `disabled` attribute will be set on the element
*/
+13 -10
View File
@@ -163,6 +163,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
forEach(form.$error, function(value, name) {
form.$setValidity(name, null, control);
});
forEach(form.$$success, function(value, name) {
form.$setValidity(name, null, control);
});
arrayRemove(controls, control);
};
@@ -180,23 +183,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
addSetValidityMethod({
ctrl: this,
$element: element,
set: function(object, property, control) {
set: function(object, property, controller) {
var list = object[property];
if (!list) {
object[property] = [control];
object[property] = [controller];
} else {
var index = list.indexOf(control);
var index = list.indexOf(controller);
if (index === -1) {
list.push(control);
list.push(controller);
}
}
},
unset: function(object, property, control) {
unset: function(object, property, controller) {
var list = object[property];
if (!list) {
return;
}
arrayRemove(list, control);
arrayRemove(list, controller);
if (list.length === 0) {
delete object[property];
}
@@ -489,19 +492,19 @@ var formDirectiveFactory = function(isNgForm) {
alias = controller.$name;
if (alias) {
setter(scope, alias, controller, alias);
setter(scope, null, alias, controller, alias);
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
if (alias === newValue) return;
setter(scope, alias, undefined, alias);
setter(scope, null, alias, undefined, alias);
alias = newValue;
setter(scope, alias, controller, alias);
setter(scope, null, alias, controller, alias);
parentFormCtrl.$$renameControl(controller, alias);
});
}
formElement.on('$destroy', function() {
parentFormCtrl.$removeControl(controller);
if (alias) {
setter(scope, alias, undefined, alias);
setter(scope, null, alias, undefined, alias);
}
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
});
+96 -1686
View File
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
'use strict';
/**
* @ngdoc directive
* @name ngChange
*
* @description
* Evaluate the given expression when the user changes the input.
* The expression is evaluated immediately, unlike the JavaScript onchange event
* which only triggers at the end of a change (usually, when the user leaves the
* form element or presses the return key).
*
* The `ngChange` expression is only evaluated when a change in the input value causes
* a new value to be committed to the model.
*
* It will not be evaluated:
* * if the value returned from the `$parsers` transformation pipeline has not changed
* * if the input has continued to be invalid since the model will stay `null`
* * if the model is changed programmatically and not by a change to the input value
*
*
* Note, this directive requires `ngModel` to be present.
*
* @element input
* @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
* in input value.
*
* @example
* <example name="ngChange-directive" module="changeExample">
* <file name="index.html">
* <script>
* angular.module('changeExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.counter = 0;
* $scope.change = function() {
* $scope.counter++;
* };
* }]);
* </script>
* <div ng-controller="ExampleController">
* <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 />
* <tt>debug = {{confirmed}}</tt><br/>
* <tt>counter = {{counter}}</tt><br/>
* </div>
* </file>
* <file name="protractor.js" type="protractor">
* var counter = element(by.binding('counter'));
* var debug = element(by.binding('confirmed'));
*
* it('should evaluate the expression if changing from view', function() {
* 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(by.id('ng-change-example2')).click();
* expect(counter.getText()).toContain('0');
* expect(debug.getText()).toContain('true');
* });
* </file>
* </example>
*/
var ngChangeDirective = valueFn({
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
ctrl.$viewChangeListeners.push(function() {
scope.$eval(attr.ngChange);
});
}
});
+3 -2
View File
@@ -141,8 +141,9 @@ function classDirective(name, selector) {
* new classes are added.
*
* @animations
* add - happens just before the class is applied to the element
* remove - happens just before the class is removed from the element
* **add** - happens just before the class is applied to the elements
*
* **remove** - happens just before the class is removed from the element
*
* @element ANY
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
+2 -2
View File
@@ -49,7 +49,7 @@
<select ng-model="template" ng-options="t.name for t in templates">
<option value="">(blank)</option>
</select>
url of the template: <tt>{{template.url}}</tt>
url of the template: <code>{{template.url}}</code>
<hr/>
<div class="slide-animate-container">
<div class="slide-animate" ng-include="template.url"></div>
@@ -173,7 +173,7 @@
* @name ngInclude#$includeContentError
* @eventType emit on the scope ngInclude was declared in
* @description
* Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299)
* Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
*
* @param {Object} angularEvent Synthetic event object.
* @param {String} src URL of content to load.
+1 -1
View File
@@ -19,7 +19,7 @@
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
* sure you have parenthesis for correct precedence:
* <pre class="prettyprint">
* <div ng-init="test1 = (data | orderBy:'name')"></div>
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
* </pre>
* </div>
*
+128
View File
@@ -0,0 +1,128 @@
'use strict';
/**
* @ngdoc directive
* @name ngList
*
* @description
* Text input that converts between a delimited string and an array of strings. The default
* delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
* delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
*
* The behaviour of the directive is affected by the use of the `ngTrim` attribute.
* * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
* list item is respected. This implies that the user of the directive is responsible for
* dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
* tab or newline character.
* * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
* when joining the list items back together) and whitespace around each list item is stripped
* before it is added to the model.
*
* ### Example with Validation
*
* <example name="ngList-directive" module="listExample">
* <file name="app.js">
* angular.module('listExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.names = ['morpheus', 'neo', 'trinity'];
* }]);
* </file>
* <file name="index.html">
* <form name="myForm" ng-controller="ExampleController">
* List: <input name="namesInput" ng-model="names" ng-list required>
* <span class="error" ng-show="myForm.namesInput.$error.required">
* Required!</span>
* <br>
* <tt>names = {{names}}</tt><br/>
* <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
* <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
* <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
* <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
* </form>
* </file>
* <file name="protractor.js" type="protractor">
* var listInput = element(by.model('names'));
* var names = element(by.exactBinding('names'));
* var valid = element(by.binding('myForm.namesInput.$valid'));
* var error = element(by.css('span.error'));
*
* it('should initialize to model', function() {
* expect(names.getText()).toContain('["morpheus","neo","trinity"]');
* expect(valid.getText()).toContain('true');
* expect(error.getCssValue('display')).toBe('none');
* });
*
* it('should be invalid if empty', function() {
* listInput.clear();
* listInput.sendKeys('');
*
* expect(names.getText()).toContain('');
* expect(valid.getText()).toContain('false');
* expect(error.getCssValue('display')).not.toBe('none');
* });
* </file>
* </example>
*
* ### Example - splitting on whitespace
* <example name="ngList-directive-newlines">
* <file name="index.html">
* <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
* <pre>{{ list | json }}</pre>
* </file>
* <file name="protractor.js" type="protractor">
* it("should split the text by newlines", function() {
* var listInput = element(by.model('list'));
* var output = element(by.binding('list | json'));
* listInput.sendKeys('abc\ndef\nghi');
* expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
* });
* </file>
* </example>
*
* @element input
* @param {string=} ngList optional delimiter that should be used to split the value.
*/
var ngListDirective = function() {
return {
restrict: 'A',
priority: 100,
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
// We want to control whitespace trimming so we use this convoluted approach
// to access the ngList attribute, which doesn't pre-trim the attribute
var ngList = element.attr(attr.$attr.ngList) || ', ';
var trimValues = attr.ngTrim !== 'false';
var separator = trimValues ? trim(ngList) : ngList;
var parse = function(viewValue) {
// If the viewValue is invalid (say required but empty) it will be `undefined`
if (isUndefined(viewValue)) return;
var list = [];
if (viewValue) {
forEach(viewValue.split(separator), function(value) {
if (value) list.push(trimValues ? trim(value) : value);
});
}
return list;
};
ctrl.$parsers.push(parse);
ctrl.$formatters.push(function(value) {
if (isArray(value)) {
return value.join(ngList);
}
return undefined;
});
// Override the standard $isEmpty because an empty array means the input is empty.
ctrl.$isEmpty = function(value) {
return !value || !value.length;
};
}
};
};
File diff suppressed because it is too large Load Diff
+79 -7
View File
@@ -23,6 +23,78 @@
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
* This may be useful when, for instance, nesting ngRepeats.
*
* # Iterating over object properties
*
* It is possible to get `ngRepeat` to iterate over the properties of an object using the following
* syntax:
*
* ```js
* <div ng-repeat="(key, value) in myObj"> ... </div>
* ```
*
* You need to be aware that the JavaScript specification does not define what order
* it will return the keys for an object. In order to have a guaranteed deterministic order
* for the keys, Angular versions up to and including 1.3 **sort the keys alphabetically**.
*
* If this is not desired, the recommended workaround is to convert your object into an array
* that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
* do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
* or implement a `$watch` on the object yourself.
*
* In version 1.4 we will remove the sorting, since it seems that browsers generally follow the
* strategy of providing keys in the order in which they were defined, although there are exceptions
* when keys are deleted and reinstated.
*
*
* # Tracking and Duplicates
*
* When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
*
* * When an item is added, a new instance of the template is added to the DOM.
* * When an item is removed, its template instance is removed from the DOM.
* * When items are reordered, their respective templates are reordered in the DOM.
*
* By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
* there are duplicates, it is not possible to maintain a one-to-one mapping between collection
* items and DOM elements.
*
* If you do need to repeat duplicate items, you can substitute the default tracking behavior
* with your own using the `track by` expression.
*
* For example, you may track items by the index of each item in the collection, using the
* special scope property `$index`:
* ```html
* <div ng-repeat="n in [42, 42, 43, 43] track by $index">
* {{n}}
* </div>
* ```
*
* You may use arbitrary expressions in `track by`, including references to custom functions
* on the scope:
* ```html
* <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
* {{n}}
* </div>
* ```
*
* If you are working with objects that have an identifier property, you can track
* by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
* will not have to rebuild the DOM elements for items it has already rendered, even if the
* JavaScript objects in the collection have been substituted for new ones:
* ```html
* <div ng-repeat="model in collection track by model.id">
* {{model.name}}
* </div>
* ```
*
* When no `track by` expression is provided, it is equivalent to tracking by the built-in
* `$id` function, which tracks items by their identity:
* ```html
* <div ng-repeat="obj in collection track by $id(obj)">
* {{obj.prop}}
* </div>
* ```
*
* # Special repeat start and end points
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
@@ -90,12 +162,12 @@
*
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
*
* * `variable in expression track by tracking_expression` You can also provide an optional tracking function
* which can be used to associate the objects in the collection with the DOM elements. If no tracking function
* is specified the ng-repeat associates elements by identity in the collection. It is an error to have
* more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
* mapped to the same DOM element, which is not possible.) Filters should be applied to the expression,
* before specifying a tracking expression.
* * `variable in expression track by tracking_expression` You can also provide an optional tracking expression
* which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
* is specified, ng-repeat associates elements by identity. It is an error to have
* more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
* mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be
* applied before the tracking expression.
*
* For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
* will be associated by item identity in the array.
@@ -267,7 +339,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var keyIdentifier = match[2];
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(aliasAs))) {
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
aliasAs);
}
+3 -2
View File
@@ -40,10 +40,11 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
*
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS:
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
* with extra animation classes that can be added.
*
* ```css
* .ng-hide {
* .ng-hide:not(.ng-hide-animate) {
* /&#42; this is just another form of hiding an element &#42;/
* display: block!important;
* position: absolute;
+2 -2
View File
@@ -47,10 +47,10 @@
</example>
*/
var ngStyleDirective = ngDirective(function(scope, element, attr) {
scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
if (oldStyles && (newStyles !== oldStyles)) {
forEach(oldStyles, function(val, style) { element.css(style, '');});
}
if (newStyles) element.css(newStyles);
}, true);
});
});
+91
View File
@@ -0,0 +1,91 @@
'use strict';
var requiredDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
attr.required = true; // force truthy in case we are on non input element
ctrl.$validators.required = function(modelValue, viewValue) {
return !attr.required || !ctrl.$isEmpty(viewValue);
};
attr.$observe('required', function() {
ctrl.$validate();
});
}
};
};
var patternDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var regexp, patternExp = attr.ngPattern || attr.pattern;
attr.$observe('pattern', function(regex) {
if (isString(regex) && regex.length > 0) {
regex = new RegExp('^' + regex + '$');
}
if (regex && !regex.test) {
throw minErr('ngPattern')('noregexp',
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
regex, startingTag(elm));
}
regexp = regex || undefined;
ctrl.$validate();
});
ctrl.$validators.pattern = function(value) {
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
};
}
};
};
var maxlengthDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var maxlength = -1;
attr.$observe('maxlength', function(value) {
var intVal = int(value);
maxlength = isNaN(intVal) ? -1 : intVal;
ctrl.$validate();
});
ctrl.$validators.maxlength = function(modelValue, viewValue) {
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
};
}
};
};
var minlengthDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var minlength = 0;
attr.$observe('minlength', function(value) {
minlength = int(value) || 0;
ctrl.$validate();
});
ctrl.$validators.minlength = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
};
}
};
};
+1 -1
View File
@@ -186,7 +186,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
} else if (actualType === 'array') {
} else if (isArray(actual)) {
// In case `actual` is an array, consider it a match
// if ANY of it's items matches `expected`
return actual.some(function(item) {
+1 -1
View File
@@ -353,7 +353,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
* * `'m'`: Minute in hour (0-59)
* * `'ss'`: Second in minute, padded (00-59)
* * `'s'`: Second in minute (0-59)
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
* * `'sss'`: Millisecond in second, padded (000-999)
* * `'a'`: AM/PM marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
* * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
+2 -2
View File
@@ -837,7 +837,7 @@ function $LocationProvider() {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
var elm = jqLite(event.target);
@@ -879,7 +879,7 @@ function $LocationProvider() {
// rewrite hashbang url <> html5 url
if ($location.absUrl() != initialUrl) {
if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
$browser.url($location.absUrl(), true);
}
+13 -7
View File
@@ -664,7 +664,7 @@ Parser.prototype = {
}, {
assign: function(scope, value, locals) {
var o = object(scope, locals);
if (!o) object.assign(scope, o = {});
if (!o) object.assign(scope, o = {}, locals);
return getter.assign(o, value);
}
});
@@ -690,7 +690,7 @@ Parser.prototype = {
var key = ensureSafeMemberName(indexFn(self, locals), expression);
// prevent overwriting of Function.constructor which would break ensureSafeObject check
var o = ensureSafeObject(obj(self, locals), expression);
if (!o) obj.assign(self, o = {});
if (!o) obj.assign(self, o = {}, locals);
return o[key] = value;
}
});
@@ -728,6 +728,11 @@ Parser.prototype = {
? fn.apply(context, args)
: fn(args[0], args[1], args[2], args[3], args[4]);
if (args) {
// Free-up the memory (arguments of the last function call).
args.length = 0;
}
return ensureSafeObject(v, expressionText);
};
},
@@ -800,18 +805,19 @@ Parser.prototype = {
// Parser helper functions
//////////////////////////////////////////////////
function setter(obj, path, setValue, fullExp) {
function setter(obj, locals, path, setValue, fullExp) {
ensureSafeObject(obj, fullExp);
ensureSafeObject(locals, fullExp);
var element = path.split('.'), key;
for (var i = 0; element.length > 1; i++) {
key = ensureSafeMemberName(element.shift(), fullExp);
var propertyObj = ensureSafeObject(obj[key], fullExp);
var propertyObj = (i === 0 && locals && locals[key]) || obj[key];
if (!propertyObj) {
propertyObj = {};
obj[key] = propertyObj;
}
obj = propertyObj;
obj = ensureSafeObject(propertyObj, fullExp);
}
key = ensureSafeMemberName(element.shift(), fullExp);
ensureSafeObject(obj[key], fullExp);
@@ -938,8 +944,8 @@ function getterFn(path, options, fullExp) {
}
fn.sharedGetter = true;
fn.assign = function(self, value) {
return setter(self, path, value, path);
fn.assign = function(self, value, locals) {
return setter(self, locals, path, value, path);
};
getterFnCache[path] = fn;
return fn;
+1 -2
View File
@@ -347,8 +347,7 @@ function qFactory(nextTick, exceptionHandler) {
'qcycle',
"Expected promise to be resolved with value other than itself '{0}'",
val));
}
else {
} else {
this.$$resolve(val);
}
+1 -1
View File
@@ -1045,7 +1045,7 @@ function $RootScopeProvider() {
* @kind function
*
* @description
* Schedule the invokation of $apply to occur at a later time. The actual time difference
* Schedule the invocation of $apply to occur at a later time. The actual time difference
* varies across browsers, but is typically around ~10 milliseconds.
*
* This can be used to queue up multiple expressions which need to be evaluated in the same
+4 -4
View File
@@ -22,8 +22,7 @@ var $compileMinErr = minErr('$compile');
function $TemplateRequestProvider() {
this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) {
function handleRequestFn(tpl, ignoreRequestError) {
var self = handleRequestFn;
self.totalPendingRequests++;
handleRequestFn.totalPendingRequests++;
var transformResponse = $http.defaults && $http.defaults.transformResponse;
@@ -41,13 +40,14 @@ function $TemplateRequestProvider() {
};
return $http.get(tpl, httpOptions)
.finally(function() {
handleRequestFn.totalPendingRequests--;
})
.then(function(response) {
self.totalPendingRequests--;
return response.data;
}, handleError);
function handleError(resp) {
self.totalPendingRequests--;
if (!ignoreRequestError) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
}
+2 -3
View File
@@ -1327,8 +1327,7 @@ angular.module('ngAnimate', ['ng'])
} else if (lastAnimation.event == 'setClass') {
animationsToCancel.push(lastAnimation);
cleanup(element, className);
}
else if (runningAnimations[className]) {
} else if (runningAnimations[className]) {
var current = runningAnimations[className];
if (current.event == animationEvent) {
skipAnimation = true;
@@ -1869,7 +1868,7 @@ angular.module('ngAnimate', ['ng'])
return;
}
if (!staggerTime && styles) {
if (!staggerTime && styles && Object.keys(styles).length > 0) {
if (!timings.transitionDuration) {
element.css('transition', timings.animationDuration + 's linear all');
appliedStyles.push('transition');
+15 -5
View File
@@ -100,7 +100,8 @@ function $AriaProvider() {
* - **ariaMultiline** `{boolean}` Enables/disables aria-multiline tags
* - **ariaValue** `{boolean}` Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
* - **tabindex** `{boolean}` Enables/disables tabindex tags
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on ng-click
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on `&lt;div&gt;` and
* `&lt;li&gt;` elements with ng-click
*
* @description
* Enables/disables various ARIA attributes
@@ -128,6 +129,7 @@ function $AriaProvider() {
* @name $aria
*
* @description
* @priority 200
*
* The $aria service contains helper methods for applying common
* [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives.
@@ -204,6 +206,7 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return {
restrict: 'A',
require: '?ngModel',
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
link: function(scope, elem, attr, ngModel) {
var shape = getShape(attr, elem);
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
@@ -216,19 +219,19 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
if (needsTabIndex) {
needsTabIndex = false;
return function ngAriaRadioReaction(newVal) {
var boolVal = newVal === attr.value;
var boolVal = (attr.value == ngModel.$viewValue);
elem.attr('aria-checked', boolVal);
elem.attr('tabindex', 0 - !boolVal);
};
} else {
return function ngAriaRadioReaction(newVal) {
elem.attr('aria-checked', newVal === attr.value);
elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
};
}
}
function ngAriaCheckboxReaction(newVal) {
elem.attr('aria-checked', !!newVal);
elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
}
switch (shape) {
@@ -303,11 +306,18 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
compile: function(elem, attr) {
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
return function(scope, elem, attr) {
function isNodeOneOf(elem, nodeTypeArray) {
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
return true;
}
}
if ($aria.config('tabindex') && !elem.attr('tabindex')) {
elem.attr('tabindex', 0);
}
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
if ($aria.config('bindKeypress') && !attr.ngKeypress && isNodeOneOf(elem, ['DIV', 'LI'])) {
elem.on('keypress', function(event) {
if (event.keyCode === 32 || event.keyCode === 13) {
scope.$apply(callback);
+1 -1
View File
@@ -81,7 +81,7 @@ $provide.value("$locale", {
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "Lt",
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
+1 -1
View File
@@ -81,7 +81,7 @@ $provide.value("$locale", {
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "Lt",
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
+14 -14
View File
@@ -40,26 +40,26 @@ $provide.value("$locale", {
"\u1005\u1014\u1031"
],
"SHORTMONTH": [
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
"\u1007\u1014\u103a",
"\u1016\u1031",
"\u1019\u1010\u103a",
"\u1027\u1015\u103c\u102e",
"\u1019\u1031",
"\u1007\u103d\u1014\u103a",
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
"\u1029\u1002\u102f\u1010\u103a",
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
"\u1007\u1030",
"\u1029",
"\u1005\u1000\u103a",
"\u1021\u1031\u102c\u1000\u103a",
"\u1014\u102d\u102f",
"\u1012\u102e"
],
"fullDate": "EEEE, y MMMM dd",
"longDate": "y MMMM d",
"medium": "y MMM d HH:mm:ss",
"mediumDate": "y MMM d",
"fullDate": "EEEE, dd MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "yy/MM/dd HH:mm",
"shortDate": "yy/MM/dd",
"short": "dd-MM-yy HH:mm",
"shortDate": "dd-MM-yy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
+14 -14
View File
@@ -40,26 +40,26 @@ $provide.value("$locale", {
"\u1005\u1014\u1031"
],
"SHORTMONTH": [
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
"\u1007\u1014\u103a",
"\u1016\u1031",
"\u1019\u1010\u103a",
"\u1027\u1015\u103c\u102e",
"\u1019\u1031",
"\u1007\u103d\u1014\u103a",
"\u1007\u1030\u101c\u102d\u102f\u1004\u103a",
"\u1029\u1002\u102f\u1010\u103a",
"\u1005\u1000\u103a\u1010\u1004\u103a\u1018\u102c",
"\u1021\u1031\u102c\u1000\u103a\u1010\u102d\u102f\u1018\u102c",
"\u1014\u102d\u102f\u101d\u1004\u103a\u1018\u102c",
"\u1012\u102e\u1007\u1004\u103a\u1018\u102c"
"\u1007\u1030",
"\u1029",
"\u1005\u1000\u103a",
"\u1021\u1031\u102c\u1000\u103a",
"\u1014\u102d\u102f",
"\u1012\u102e"
],
"fullDate": "EEEE, y MMMM dd",
"longDate": "y MMMM d",
"medium": "y MMM d HH:mm:ss",
"mediumDate": "y MMM d",
"fullDate": "EEEE, dd MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "yy/MM/dd HH:mm",
"shortDate": "yy/MM/dd",
"short": "dd-MM-yy HH:mm",
"shortDate": "dd-MM-yy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
+1 -1
View File
@@ -173,7 +173,7 @@ angular.module('ngMessages', [])
* at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the ng-messages-multiple on the directive container.)
*
* A remote template can also be used to promote message reuseability and messages can also be
* A remote template can also be used to promote message reusability and messages can also be
* overridden.
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
+25 -11
View File
@@ -243,31 +243,31 @@ angular.mock.$ExceptionHandlerProvider = function() {
*
* @param {string} mode Mode of operation, defaults to `rethrow`.
*
* - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
* is a bug in the application or test, so this mock will make these tests fail.
* - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
* mode stores an array of errors in `$exceptionHandler.errors`, to allow later
* assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
* {@link ngMock.$log#reset reset()}
* - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
* is a bug in the application or test, so this mock will make these tests fail.
* For any implementations that expect exceptions to be thrown, the `rethrow` mode
* will also maintain a log of thrown errors.
*/
this.mode = function(mode) {
switch (mode) {
case 'rethrow':
handler = function(e) {
throw e;
};
break;
case 'log':
var errors = [];
switch (mode) {
case 'log':
case 'rethrow':
var errors = [];
handler = function(e) {
if (arguments.length == 1) {
errors.push(e);
} else {
errors.push([].slice.call(arguments, 0));
}
if (mode === "rethrow") {
throw e;
}
};
handler.errors = errors;
break;
default:
@@ -2127,18 +2127,32 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
if (window.jasmine || window.mocha) {
var currentSpec = null,
annotatedFunctions = [],
isSpecRunning = function() {
return !!currentSpec;
};
angular.mock.$$annotate = angular.injector.$$annotate;
angular.injector.$$annotate = function(fn) {
if (typeof fn === 'function' && !fn.$inject) {
annotatedFunctions.push(fn);
}
return angular.mock.$$annotate.apply(this, arguments);
};
(window.beforeEach || window.setup)(function() {
annotatedFunctions = [];
currentSpec = this;
});
(window.afterEach || window.teardown)(function() {
var injector = currentSpec.$injector;
annotatedFunctions.forEach(function(fn) {
delete fn.$inject;
});
angular.forEach(currentSpec.$modules, function(module) {
if (module && module.$$hashKey) {
module.$$hashKey = undefined;
+1
View File
@@ -208,6 +208,7 @@ function shallowClearAndCopy(src, dst) {
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
*
*
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
* with (httpResponse) argument.
*
+4 -10
View File
@@ -477,21 +477,15 @@ function $RouteProvider() {
* definitions will be interpolated into the location's path, while
* remaining properties will be treated as query params.
*
* @param {Object} newParams mapping of URL parameter names to values
* @param {!Object<string, string>} newParams mapping of URL parameter names to values
*/
updateParams: function(newParams) {
if (this.current && this.current.$$route) {
var searchParams = {}, self=this;
angular.forEach(Object.keys(newParams), function(key) {
if (!self.current.pathParams[key]) searchParams[key] = newParams[key];
});
newParams = angular.extend({}, this.current.params, newParams);
$location.path(interpolate(this.current.$$route.originalPath, newParams));
$location.search(angular.extend({}, $location.search(), searchParams));
}
else {
// interpolate modifies newParams, only query params are left
$location.search(newParams);
} else {
throw $routeMinErr('norout', 'Tried updating route when with no current route');
}
}
+14 -26
View File
@@ -271,14 +271,14 @@ function htmlParser(html, handler) {
}
}
var index, chars, match, stack = [], last = html, text;
stack.last = function() { return stack[ stack.length - 1 ]; };
stack.last = function() { return stack[stack.length - 1]; };
while (html) {
text = '';
chars = true;
// Make sure we're not in a script or style element
if (!stack.last() || !specialElements[ stack.last() ]) {
if (!stack.last() || !specialElements[stack.last()]) {
// Comment
if (html.indexOf("<!--") === 0) {
@@ -336,7 +336,8 @@ function htmlParser(html, handler) {
}
} else {
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
// IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
function(all, text) {
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
@@ -360,17 +361,17 @@ function htmlParser(html, handler) {
function parseStartTag(tag, tagName, rest, unary) {
tagName = angular.lowercase(tagName);
if (blockElements[ tagName ]) {
while (stack.last() && inlineElements[ stack.last() ]) {
if (blockElements[tagName]) {
while (stack.last() && inlineElements[stack.last()]) {
parseEndTag("", stack.last());
}
}
if (optionalEndTagElements[ tagName ] && stack.last() == tagName) {
if (optionalEndTagElements[tagName] && stack.last() == tagName) {
parseEndTag("", tagName);
}
unary = voidElements[ tagName ] || !!unary;
unary = voidElements[tagName] || !!unary;
if (!unary)
stack.push(tagName);
@@ -395,13 +396,13 @@ function htmlParser(html, handler) {
if (tagName)
// Find the closest opened tag of the same type
for (pos = stack.length - 1; pos >= 0; pos--)
if (stack[ pos ] == tagName)
if (stack[pos] == tagName)
break;
if (pos >= 0) {
// Close all the open elements, up the stack
for (i = stack.length - 1; i >= pos; i--)
if (handler.end) handler.end(stack[ i ]);
if (handler.end) handler.end(stack[i]);
// Remove the open elements from the stack
stack.length = pos;
@@ -410,7 +411,6 @@ function htmlParser(html, handler) {
}
var hiddenPre=document.createElement("pre");
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
@@ -419,22 +419,10 @@ var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
function decodeEntities(value) {
if (!value) { return ''; }
// Note: IE8 does not preserve spaces at the start/end of innerHTML
// so we must capture them and reattach them afterward
var parts = spaceRe.exec(value);
var spaceBefore = parts[1];
var spaceAfter = parts[3];
var content = parts[2];
if (content) {
hiddenPre.innerHTML=content.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary
// reflows. However, IE<9 don't support textContent so the innerText
// fallback is necessary.
content = 'textContent' in hiddenPre ?
hiddenPre.textContent : hiddenPre.innerText;
}
return spaceBefore + content + spaceAfter;
hiddenPre.innerHTML = value.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary reflows.
return hiddenPre.textContent;
}
/**
+29 -10
View File
@@ -68,19 +68,31 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
try {
var $window = self.getWindow_();
if ($window.angular) {
// Disable animations
$window.angular.resumeBootstrap([['$provide', function($provide) {
return ['$animate', function($animate) {
$animate.enabled(false);
}];
}]]);
if (!$window.angular) {
self.executeAction(loadFn);
return;
}
if (!$window.angular.resumeBootstrap) {
$window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
} else {
resumeDeferredBootstrap();
}
self.executeAction(loadFn);
} catch (e) {
errorFn(e);
}
function resumeDeferredBootstrap() {
// Disable animations
var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) {
return ['$animate', function($animate) {
$animate.enabled(false);
}];
}]]);
self.rootElement = $injector.get('$rootElement')[0];
self.executeAction(loadFn);
}
}).attr('src', url);
// for IE compatibility set the name *after* setting the frame url
@@ -105,7 +117,14 @@ angular.scenario.Application.prototype.executeAction = function(action) {
if (!$window.angular) {
return action.call(this, $window, _jQuery($window.document));
}
angularInit($window.document, function(element) {
if (!!this.rootElement) {
executeWithElement(this.rootElement);
} else {
angularInit($window.document, angular.bind(this, executeWithElement));
}
function executeWithElement(element) {
var $injector = $window.angular.element(element).injector();
var $element = _jQuery(element);
@@ -118,5 +137,5 @@ angular.scenario.Application.prototype.executeAction = function(action) {
action.call(self, $window, $element);
});
});
});
}
};
+1 -1
View File
@@ -304,7 +304,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
var element = windowJquery(this),
bindings;
if (bindings = element.data('$binding')) {
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
binding = bindings[j];
if (binding.expressions) {
+4 -8
View File
@@ -55,8 +55,7 @@
if (window.WebKitTransitionEvent) {
evnt = new WebKitTransitionEvent(eventType, eventData);
evnt.initEvent(eventType, false, true);
}
else {
} else {
try {
evnt = new TransitionEvent(eventType, eventData);
}
@@ -65,13 +64,11 @@
evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
}
else if (/animationend/.test(eventType)) {
} else if (/animationend/.test(eventType)) {
if (window.WebKitAnimationEvent) {
evnt = new WebKitAnimationEvent(eventType, eventData);
evnt.initEvent(eventType, false, true);
}
else {
} else {
try {
evnt = new AnimationEvent(eventType, eventData);
}
@@ -80,8 +77,7 @@
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
}
else {
} else {
evnt = document.createEvent('MouseEvents');
x = x || 0;
y = y || 0;
+20
View File
@@ -1081,6 +1081,26 @@ describe('angular', function() {
window.name = originalName;
});
it('should provide injector for deferred bootstrap', function() {
var injector;
window.name = 'NG_DEFER_BOOTSTRAP!';
injector = angular.bootstrap(element);
expect(injector).toBeUndefined();
injector = angular.resumeBootstrap();
expect(injector).toBeDefined();
});
it('should resume deferred bootstrap, if defined', function() {
var injector;
window.name = 'NG_DEFER_BOOTSTRAP!';
angular.resumeDeferredBootstrap = noop;
var spy = spyOn(angular, "resumeDeferredBootstrap");
injector = angular.bootstrap(element);
expect(spy).toHaveBeenCalled();
});
it('should wait for extra modules', function() {
window.name = 'NG_DEFER_BOOTSTRAP!';
+7 -3
View File
@@ -238,7 +238,11 @@ describe('injector', function() {
it('should publish annotate API', function() {
expect(injector.annotate).toBe(annotate);
expect(angular.mock.$$annotate).toBe(annotate);
spyOn(angular.mock, '$$annotate').andCallThrough();
function fn() {}
injector.annotate(fn);
expect(angular.mock.$$annotate).toHaveBeenCalledWith(fn);
});
});
@@ -972,7 +976,7 @@ describe('strict-di injector', function() {
});
});
inject(function($injector) {
expect (function() {
expect(function() {
$injector.invoke(function($test2) {});
}).toThrowMinErr('$injector', 'strictdi');
});
@@ -986,7 +990,7 @@ describe('strict-di injector', function() {
});
});
inject(function($injector) {
expect (function() {
expect(function() {
$injector.invoke(['$test', function($test) {}]);
}).toThrowMinErr('$injector', 'strictdi');
});
+16 -9
View File
@@ -9,22 +9,29 @@ describe('private mocks', function() {
var doc = $document[0];
var count = doc.styleSheets.length;
var stylesheet = createMockStyleSheet($document, $window);
expect(doc.styleSheets.length).toBe(count + 1);
var elm;
runs(function() {
expect(doc.styleSheets.length).toBe(count + 1);
angular.element(doc.body).append($rootElement);
angular.element(doc.body).append($rootElement);
var elm = $compile('<div class="padded">...</div>')($rootScope);
$rootElement.append(elm);
elm = $compile('<div class="padded">...</div>')($rootScope);
$rootElement.append(elm);
expect(getStyle(elm, 'paddingTop')).toBe('0px');
expect(getStyle(elm, 'paddingTop')).toBe('0px');
stylesheet.addRule('.padded', 'padding-top:2px');
stylesheet.addRule('.padded', 'padding-top:2px');
});
expect(getStyle(elm, 'paddingTop')).toBe('2px');
waitsFor(function() {
return getStyle(elm, 'paddingTop') === '2px';
});
stylesheet.destroy();
runs(function() {
stylesheet.destroy();
expect(getStyle(elm, 'paddingTop')).toBe('0px');
expect(getStyle(elm, 'paddingTop')).toBe('0px');
});
function getStyle(element, key) {
var node = element[0];
+64
View File
@@ -339,3 +339,67 @@ window.dump = function() {
return angular.mock.dump(arg);
}));
};
function getInputCompileHelper(currentSpec) {
var helper = {};
module(function($compileProvider) {
$compileProvider.directive('attrCapture', function() {
return function(scope, element, $attrs) {
helper.attrs = $attrs;
};
});
});
inject(function($compile, $rootScope, $sniffer) {
helper.compileInput = function(inputHtml, mockValidity, scope) {
scope = helper.scope = scope || $rootScope;
// Create the input element and dealoc when done
helper.inputElm = jqLite(inputHtml);
// Set up mock validation if necessary
if (isObject(mockValidity)) {
VALIDITY_STATE_PROPERTY = 'ngMockValidity';
helper.inputElm.prop(VALIDITY_STATE_PROPERTY, mockValidity);
currentSpec.after(function() {
VALIDITY_STATE_PROPERTY = 'validity';
});
}
// Create the form element and dealoc when done
helper.formElm = jqLite('<form name="form"></form>');
helper.formElm.append(helper.inputElm);
// Compile the lot and return the input element
$compile(helper.formElm)(scope);
spyOn(scope.form, '$addControl').andCallThrough();
spyOn(scope.form, '$$renameControl').andCallThrough();
scope.$digest();
return helper.inputElm;
};
helper.changeInputValueTo = function(value) {
helper.inputElm.val(value);
browserTrigger(helper.inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
};
helper.changeGivenInputTo = function(inputElm, value) {
inputElm.val(value);
browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
};
helper.dealoc = function() {
dealoc(helper.inputElm);
dealoc(helper.formElm);
};
});
return helper;
}
+2 -2
View File
@@ -395,7 +395,7 @@ describe('browser', function() {
});
it ('should return a value for an existing cookie', function() {
it('should return a value for an existing cookie', function() {
document.cookie = "foo=bar=baz;path=/";
expect(browser.cookies().foo).toEqual('bar=baz');
});
@@ -408,7 +408,7 @@ describe('browser', function() {
expect(browser.cookies()['foo']).toBe('"first"');
});
it ('should decode cookie values that were encoded by puts', function() {
it('should decode cookie values that were encoded by puts', function() {
document.cookie = "cookie2%3Dbar%3Bbaz=val%3Due;path=/";
expect(browser.cookies()['cookie2=bar;baz']).toEqual('val=ue');
});
+70 -19
View File
@@ -423,8 +423,7 @@ describe('$compile', function() {
describe('multiple directives per element', function() {
it('should allow multiple directives per element', inject(function($compile, $rootScope, log) {
element = $compile(
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')
($rootScope);
'<span greet="angular" log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
expect(element.text()).toEqual('Hello angular');
expect(log).toEqual('L; M; H');
}));
@@ -443,6 +442,17 @@ describe('$compile', function() {
}));
it('should allow directives in SVG element classes', inject(function($compile, $rootScope, log) {
if (!window.SVGElement) return;
element = $compile('<svg><text class="greet: angular; log:123;"></text></svg>')($rootScope);
var text = element.children().eq(0);
// In old Safari, SVG elements don't have innerHTML, so element.html() won't work
// (https://bugs.webkit.org/show_bug.cgi?id=136903)
expect(text.text()).toEqual('Hello angular');
expect(log).toEqual('123');
}));
it('should ignore not set CSS classes on SVG elements', inject(function($compile, $rootScope, log) {
if (!window.SVGElement) return;
// According to spec SVG element className property is readonly, but only FF
@@ -596,8 +606,7 @@ describe('$compile', function() {
describe('priority', function() {
it('should honor priority', inject(function($compile, $rootScope, log) {
element = $compile(
'<span log="L" x-high-log="H" data-medium-log="M"></span>')
($rootScope);
'<span log="L" x-high-log="H" data-medium-log="M"></span>')($rootScope);
expect(log).toEqual('L; M; H');
}));
});
@@ -798,8 +807,7 @@ describe('$compile', function() {
it('should compile template when replacing', inject(function($compile, $rootScope, log) {
element = $compile('<div><div replace medium-log>ignore</div><div>')
($rootScope);
element = $compile('<div><div replace medium-log>ignore</div><div>')($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Replace!');
expect(log).toEqual('LOG; HIGH; MEDIUM');
@@ -807,8 +815,7 @@ describe('$compile', function() {
it('should compile template when appending', inject(function($compile, $rootScope, log) {
element = $compile('<div><div append medium-log>ignore</div><div>')
($rootScope);
element = $compile('<div><div append medium-log>ignore</div><div>')($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Append!');
expect(log).toEqual('LOG; HIGH; MEDIUM');
@@ -817,8 +824,7 @@ describe('$compile', function() {
it('should merge attributes including style attr', inject(function($compile, $rootScope) {
element = $compile(
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')
($rootScope);
'<div><div replace class="medium-log" style="height: 20px" ></div><div>')($rootScope);
var div = element.find('div');
expect(div.hasClass('medium-log')).toBe(true);
expect(div.hasClass('log')).toBe(true);
@@ -830,8 +836,7 @@ describe('$compile', function() {
it('should not merge attributes if they are the same', inject(function($compile, $rootScope) {
element = $compile(
'<div><div nomerge class="medium-log" id="myid"></div><div>')
($rootScope);
'<div><div nomerge class="medium-log" id="myid"></div><div>')($rootScope);
var div = element.find('div');
expect(div.hasClass('medium-log')).toBe(true);
expect(div.hasClass('log')).toBe(true);
@@ -1104,6 +1109,29 @@ describe('$compile', function() {
expect(element.find('p').text()).toBe('Hello, world!');
});
});
it('should keep prototype properties on directive', function() {
module(function() {
function DirectiveClass() {
this.restrict = 'E';
this.template = "<p>{{value}}</p>";
}
DirectiveClass.prototype.compile = function() {
return function(scope, element, attrs) {
scope.value = "Test Value";
};
};
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
});
inject(function($compile, $rootScope) {
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
$rootScope.$digest();
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
});
});
});
@@ -2023,6 +2051,32 @@ describe('$compile', function() {
});
});
it('should keep prototype properties on sync version of async directive', function() {
module(function() {
function DirectiveClass() {
this.restrict = 'E';
this.templateUrl = "test.html";
}
DirectiveClass.prototype.compile = function() {
return function(scope, element, attrs) {
scope.value = "Test Value";
};
};
directive('templateUrlWithPrototype', valueFn(new DirectiveClass()));
});
inject(function($compile, $rootScope, $httpBackend) {
$httpBackend.whenGET('test.html').
respond('<p>{{value}}</p>');
element = $compile('<template-url-with-prototype><template-url-with-prototype>')($rootScope);
$httpBackend.flush();
$rootScope.$digest();
expect(element.find("p")[0].innerHTML).toEqual("Test Value");
});
});
});
@@ -4599,8 +4653,7 @@ describe('$compile', function() {
});
});
inject(function(log, $rootScope, $compile) {
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')
($rootScope);
element = $compile('<div><div trans>T:{{x}}-{{$parent.$id}}-{{$id}}<span>;</span></div></div>')($rootScope);
$rootScope.x = 'root';
$rootScope.$apply();
expect(element.text()).toEqual('W:iso-1-2;T:root-2-3;');
@@ -4885,8 +4938,7 @@ describe('$compile', function() {
});
});
inject(function(log, $rootScope, $compile) {
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')
($rootScope);
element = $compile('<div><div trans>T:{{$$transcluded}}</div></div>')($rootScope);
$rootScope.$apply();
expect(jqLite(element.find('span')[0]).text()).toEqual('I:');
expect(jqLite(element.find('span')[1]).text()).toEqual('T:true');
@@ -5717,8 +5769,7 @@ describe('$compile', function() {
});
});
inject(function(log, $rootScope, $compile) {
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')
($rootScope);
element = $compile('<div><div high-log trans="text" log>{{$parent.$id}}-{{$id}};</div></div>')($rootScope);
$rootScope.$apply();
expect(log).toEqual('compile: <!-- trans: text -->; link; LOG; LOG; HIGH');
expect(element.text()).toEqual('1-2;1-3;');
@@ -6054,7 +6105,7 @@ describe('$compile', function() {
link: function(scope, element, attrs, ctrl, transclude) {
// We use timeout here to simulate how ng-if works
$timeout(function() {
$timeout(function() {
transclude(function(child) { element.append(child); });
});
}
+54 -1
View File
@@ -27,6 +27,18 @@ describe('$controller', function() {
expect(ctrl instanceof FooCtrl).toBe(true);
});
it('should allow registration of bound controller functions', function() {
var FooCtrl = function($scope) { $scope.foo = 'bar'; },
scope = {},
ctrl;
var BoundFooCtrl = FooCtrl.bind(null);
$controllerProvider.register('FooCtrl', ['$scope', BoundFooCtrl]);
ctrl = $controller('FooCtrl', {$scope: scope});
expect(scope.foo).toBe('bar');
});
it('should allow registration of map of controllers', function() {
var FooCtrl = function($scope) { $scope.foo = 'foo'; },
@@ -78,7 +90,16 @@ describe('$controller', function() {
var foo = $controller('a.Foo', {$scope: scope});
expect(foo).toBeDefined();
expect(foo instanceof Foo).toBe(true);
}));
}));
it('should throw ctrlfmt if name contains spaces', function() {
expect(function() {
$controller('ctrl doom');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl doom'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
});
@@ -156,5 +177,37 @@ describe('$controller', function() {
}).toThrowMinErr("$controller", "noscp", "Cannot export controller 'a.b.FooCtrl' as 'foo'! No $scope object provided via `locals`.");
});
it('should throw ctrlfmt if identifier contains non-ident characters', function() {
expect(function() {
$controller('ctrl as foo<bar');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as foo<bar'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
it('should throw ctrlfmt if identifier contains spaces', function() {
expect(function() {
$controller('ctrl as foo bar');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as foo bar'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
it('should throw ctrlfmt if identifier missing after " as "', function() {
expect(function() {
$controller('ctrl as ');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as '. " +
"Must match `__name__ as __id__` or `__name__`.");
expect(function() {
$controller('ctrl as');
}).toThrowMinErr("$controller", "ctrlfmt",
"Badly formed controller string 'ctrl as'. " +
"Must match `__name__ as __id__` or `__name__`.");
});
});
});
+53
View File
@@ -3,6 +3,25 @@
describe('a', function() {
var element, $compile, $rootScope;
beforeEach(module(function($compileProvider) {
$compileProvider.
directive('linkTo', valueFn({
restrict: 'A',
template: '<div class="my-link"><a href="{{destination}}">{{destination}}</a></div>',
replace: true,
scope: {
destination: '@linkTo'
}
})).
directive('linkNot', valueFn({
restrict: 'A',
template: '<div class="my-link"><a href>{{destination}}</a></div>',
replace: true,
scope: {
destination: '@linkNot'
}
}));
}));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
@@ -76,6 +95,40 @@ describe('a', function() {
});
it('should not preventDefault if anchor element is replaced with href-containing element', function() {
spyOn(jqLite.prototype, 'on').andCallThrough();
element = $compile('<a link-to="https://www.google.com">')($rootScope);
$rootScope.$digest();
var child = element.children('a');
var preventDefault = jasmine.createSpy('preventDefault');
child.triggerHandler({
type: 'click',
preventDefault: preventDefault
});
expect(preventDefault).not.toHaveBeenCalled();
});
it('should preventDefault if anchor element is replaced with element without href attribute', function() {
spyOn(jqLite.prototype, 'on').andCallThrough();
element = $compile('<a link-not="https://www.google.com">')($rootScope);
$rootScope.$digest();
var child = element.children('a');
var preventDefault = jasmine.createSpy('preventDefault');
child.triggerHandler({
type: 'click',
preventDefault: preventDefault
});
expect(preventDefault).toHaveBeenCalled();
});
if (isDefined(window.SVGElement)) {
describe('SVGAElement', function() {
it('should prevent default action to be executed when href is empty', function() {
+55 -3
View File
@@ -463,7 +463,7 @@ describe('form', function() {
doc = jqLite(
'<form name="parent">' +
'<div class="ng-form" name="child">' +
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required>' +
'<input ng-if="inputPresent" ng-model="modelA" name="inputA" required maxlength="10">' +
'</div>' +
'</form>');
$compile(doc)(scope);
@@ -476,23 +476,75 @@ describe('form', function() {
expect(parent).toBeDefined();
expect(child).toBeDefined();
expect(parent.$error.required).toEqual([child]);
expect(parent.$$success.maxlength).toEqual([child]);
expect(child.$error.required).toEqual([input]);
expect(child.$$success.maxlength).toEqual([input]);
expect(doc.hasClass('ng-invalid')).toBe(true);
expect(doc.hasClass('ng-invalid-required')).toBe(true);
expect(doc.hasClass('ng-valid-maxlength')).toBe(true);
expect(doc.find('div').hasClass('ng-invalid')).toBe(true);
expect(doc.find('div').hasClass('ng-invalid-required')).toBe(true);
expect(doc.find('div').hasClass('ng-valid-maxlength')).toBe(true);
//remove child input
scope.inputPresent = false;
scope.$apply();
scope.$apply('inputPresent = false');
expect(parent.$error.required).toBeFalsy();
expect(parent.$$success.maxlength).toBeFalsy();
expect(child.$error.required).toBeFalsy();
expect(child.$$success.maxlength).toBeFalsy();
expect(doc.hasClass('ng-valid')).toBe(true);
expect(doc.hasClass('ng-valid-required')).toBe(false);
expect(doc.hasClass('ng-invalid-required')).toBe(false);
expect(doc.hasClass('ng-valid-maxlength')).toBe(false);
expect(doc.hasClass('ng-invalid-maxlength')).toBe(false);
expect(doc.find('div').hasClass('ng-valid')).toBe(true);
expect(doc.find('div').hasClass('ng-valid-required')).toBe(false);
expect(doc.find('div').hasClass('ng-invalid-required')).toBe(false);
expect(doc.find('div').hasClass('ng-valid-maxlength')).toBe(false);
expect(doc.find('div').hasClass('ng-invalid-maxlength')).toBe(false);
});
it('should deregister a input that is $pending when it is removed from DOM', function() {
doc = jqLite(
'<form name="parent">' +
'<div class="ng-form" name="child">' +
'<input ng-if="inputPresent" ng-model="modelA" name="inputA">' +
'</div>' +
'</form>');
$compile(doc)(scope);
scope.$apply('inputPresent = true');
var parent = scope.parent;
var child = scope.child;
var input = child.inputA;
scope.$apply(child.inputA.$setValidity('fake', undefined));
expect(parent).toBeDefined();
expect(child).toBeDefined();
expect(parent.$pending.fake).toEqual([child]);
expect(child.$pending.fake).toEqual([input]);
expect(doc.hasClass('ng-pending')).toBe(true);
expect(doc.find('div').hasClass('ng-pending')).toBe(true);
//remove child input
scope.$apply('inputPresent = false');
expect(parent.$pending).toBeUndefined();
expect(child.$pending).toBeUndefined();
expect(doc.hasClass('ng-pending')).toBe(false);
expect(doc.find('div').hasClass('ng-pending')).toBe(false);
});
it('should leave the parent form invalid when deregister a removed input', function() {
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
'use strict';
/* globals getInputCompileHelper: false */
describe('ngChange', function() {
var helper, $rootScope;
beforeEach(function() {
helper = getInputCompileHelper(this);
});
afterEach(function() {
helper.dealoc();
});
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
}));
it('should $eval expression after new value is set in the model', function() {
helper.compileInput('<input type="text" ng-model="value" ng-change="change()" />');
$rootScope.change = jasmine.createSpy('change').andCallFake(function() {
expect($rootScope.value).toBe('new value');
});
helper.changeInputValueTo('new value');
expect($rootScope.change).toHaveBeenCalledOnce();
});
it('should not $eval the expression if changed from model', function() {
helper.compileInput('<input type="text" ng-model="value" ng-change="change()" />');
$rootScope.change = jasmine.createSpy('change');
$rootScope.$apply('value = true');
expect($rootScope.change).not.toHaveBeenCalled();
});
it('should $eval ngChange expression on checkbox', function() {
var inputElm = helper.compileInput('<input type="checkbox" ng-model="foo" ng-change="changeFn()">');
$rootScope.changeFn = jasmine.createSpy('changeFn');
expect($rootScope.changeFn).not.toHaveBeenCalled();
browserTrigger(inputElm, 'click');
expect($rootScope.changeFn).toHaveBeenCalledOnce();
});
it('should be able to change the model and via that also update the view', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-change="value=\'b\'" />');
helper.changeInputValueTo('a');
expect(inputElm.val()).toBe('b');
});
});
+9
View File
@@ -37,6 +37,10 @@ describe('ngController', function() {
this.mark = 'works';
});
var Foo = function($scope) {
$scope.mark = 'foo';
};
$controllerProvider.register('BoundFoo', ['$scope', Foo.bind(null)]);
}));
afterEach(function() {
@@ -50,6 +54,11 @@ describe('ngController', function() {
expect(element.text()).toBe('Hello Misko!');
}));
it('should instantiate bound constructor functions', inject(function($compile, $rootScope) {
element = $compile('<div ng-controller="BoundFoo">{{mark}}</div>')($rootScope);
$rootScope.$digest();
expect(element.text()).toBe('foo');
}));
it('should publish controller into scope', inject(function($compile, $rootScope) {
element = $compile('<div ng-controller="Public as p">{{p.mark}}</div>')($rootScope);
+142
View File
@@ -0,0 +1,142 @@
'use strict';
/* globals getInputCompileHelper: false */
describe('ngList', function() {
var helper, $rootScope;
beforeEach(function() {
helper = getInputCompileHelper(this);
});
afterEach(function() {
helper.dealoc();
});
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
}));
it('should parse text into an array', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list />');
// model -> view
$rootScope.$apply("list = ['x', 'y', 'z']");
expect(inputElm.val()).toBe('x, y, z');
// view -> model
helper.changeInputValueTo('1, 2, 3');
expect($rootScope.list).toEqual(['1', '2', '3']);
});
it("should not clobber text if model changes due to itself", function() {
// When the user types 'a,b' the 'a,' stage parses to ['a'] but if the
// $parseModel function runs it will change to 'a', in essence preventing
// the user from ever typing ','.
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list />');
helper.changeInputValueTo('a ');
expect(inputElm.val()).toEqual('a ');
expect($rootScope.list).toEqual(['a']);
helper.changeInputValueTo('a ,');
expect(inputElm.val()).toEqual('a ,');
expect($rootScope.list).toEqual(['a']);
helper.changeInputValueTo('a , ');
expect(inputElm.val()).toEqual('a , ');
expect($rootScope.list).toEqual(['a']);
helper.changeInputValueTo('a , b');
expect(inputElm.val()).toEqual('a , b');
expect($rootScope.list).toEqual(['a', 'b']);
});
it('should convert empty string to an empty array', function() {
helper.compileInput('<input type="text" ng-model="list" ng-list />');
helper.changeInputValueTo('');
expect($rootScope.list).toEqual([]);
});
it('should be invalid if required and empty', function() {
var inputElm = helper.compileInput('<input type="text" ng-list ng-model="list" required>');
helper.changeInputValueTo('');
expect($rootScope.list).toBeUndefined();
expect(inputElm).toBeInvalid();
helper.changeInputValueTo('a,b');
expect($rootScope.list).toEqual(['a','b']);
expect(inputElm).toBeValid();
});
describe('with a custom separator', function() {
it('should split on the custom separator', function() {
helper.compileInput('<input type="text" ng-model="list" ng-list=":" />');
helper.changeInputValueTo('a,a');
expect($rootScope.list).toEqual(['a,a']);
helper.changeInputValueTo('a:b');
expect($rootScope.list).toEqual(['a', 'b']);
});
it("should join the list back together with the custom separator", function() {
var inputElm = helper.compileInput('<input type="text" ng-model="list" ng-list=" : " />');
$rootScope.$apply(function() {
$rootScope.list = ['x', 'y', 'z'];
});
expect(inputElm.val()).toBe('x : y : z');
});
});
describe('(with ngTrim undefined or true)', function() {
it('should ignore separator whitespace when splitting', function() {
helper.compileInput('<input type="text" ng-model="list" ng-list=" | " />');
helper.changeInputValueTo('a|b');
expect($rootScope.list).toEqual(['a', 'b']);
});
it('should trim whitespace from each list item', function() {
helper.compileInput('<input type="text" ng-model="list" ng-list="|" />');
helper.changeInputValueTo('a | b');
expect($rootScope.list).toEqual(['a', 'b']);
});
});
describe('(with ngTrim set to false)', function() {
it('should use separator whitespace when splitting', function() {
helper.compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list=" | " />');
helper.changeInputValueTo('a|b');
expect($rootScope.list).toEqual(['a|b']);
helper.changeInputValueTo('a | b');
expect($rootScope.list).toEqual(['a','b']);
});
it("should not trim whitespace from each list item", function() {
helper.compileInput('<input type="text" ng-model="list" ng-trim="false" ng-list="|" />');
helper.changeInputValueTo('a | b');
expect($rootScope.list).toEqual(['a ',' b']);
});
it("should support splitting on newlines", function() {
helper.compileInput('<textarea type="text" ng-model="list" ng-trim="false" ng-list="&#10;"></textarea');
helper.changeInputValueTo('a\nb');
expect($rootScope.list).toEqual(['a','b']);
});
});
});
File diff suppressed because it is too large Load Diff
+2
View File
@@ -474,6 +474,8 @@ describe('ngRepeat', function() {
'this',
'undefined',
'$parent',
'$root',
'$id',
'$index',
'$first',
'$middle',
+549
View File
@@ -0,0 +1,549 @@
'use strict';
/* globals getInputCompileHelper: false */
describe('validators', function() {
var helper, $rootScope;
beforeEach(function() {
helper = getInputCompileHelper(this);
});
afterEach(function() {
helper.dealoc();
});
beforeEach(inject(function(_$rootScope_) {
$rootScope = _$rootScope_;
}));
describe('pattern', function() {
it('should validate in-lined pattern', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^\\d\\d\\d-\\d\\d-\\d\\d\\d\\d$/" />');
helper.changeInputValueTo('x000-00-0000x');
expect(inputElm).toBeInvalid();
helper.changeInputValueTo('000-00-0000');
expect(inputElm).toBeValid();
helper.changeInputValueTo('000-00-0000x');
expect(inputElm).toBeInvalid();
helper.changeInputValueTo('123-45-6789');
expect(inputElm).toBeValid();
helper.changeInputValueTo('x');
expect(inputElm).toBeInvalid();
});
it('should listen on ng-pattern when pattern is observed', function() {
var value, patternVal = /^\w+$/;
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="pat" attr-capture />');
helper.attrs.$observe('pattern', function(v) {
value = helper.attrs.pattern;
});
$rootScope.$apply(function() {
$rootScope.pat = patternVal;
});
expect(value).toBe(patternVal);
});
it('should validate in-lined pattern with modifiers', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^abc?$/i" />');
helper.changeInputValueTo('aB');
expect(inputElm).toBeValid();
helper.changeInputValueTo('xx');
expect(inputElm).toBeInvalid();
});
it('should validate pattern from scope', function() {
$rootScope.regexp = /^\d\d\d-\d\d-\d\d\d\d$/;
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
helper.changeInputValueTo('x000-00-0000x');
expect(inputElm).toBeInvalid();
helper.changeInputValueTo('000-00-0000');
expect(inputElm).toBeValid();
helper.changeInputValueTo('000-00-0000x');
expect(inputElm).toBeInvalid();
helper.changeInputValueTo('123-45-6789');
expect(inputElm).toBeValid();
helper.changeInputValueTo('x');
expect(inputElm).toBeInvalid();
$rootScope.$apply(function() {
$rootScope.regexp = /abc?/;
});
helper.changeInputValueTo('ab');
expect(inputElm).toBeValid();
helper.changeInputValueTo('xx');
expect(inputElm).toBeInvalid();
});
it('should perform validations when the ngPattern scope value changes', function() {
$rootScope.regexp = /^[a-z]+$/;
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
helper.changeInputValueTo('abcdef');
expect(inputElm).toBeValid();
helper.changeInputValueTo('123');
expect(inputElm).toBeInvalid();
$rootScope.$apply(function() {
$rootScope.regexp = /^\d+$/;
});
expect(inputElm).toBeValid();
helper.changeInputValueTo('abcdef');
expect(inputElm).toBeInvalid();
$rootScope.$apply(function() {
$rootScope.regexp = '';
});
expect(inputElm).toBeValid();
});
it('should register "pattern" with the model validations when the pattern attribute is used', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" pattern="^\\d+$" />');
helper.changeInputValueTo('abcd');
expect(inputElm).toBeInvalid();
expect($rootScope.form.input.$error.pattern).toBe(true);
helper.changeInputValueTo('12345');
expect(inputElm).toBeValid();
expect($rootScope.form.input.$error.pattern).not.toBe(true);
});
it('should not throw an error when scope pattern can\'t be found', function() {
expect(function() {
var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
$rootScope.$apply("foo = 'bar'");
}).not.toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
});
it('should throw an error when the scope pattern is not a regular expression', function() {
expect(function() {
var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
$rootScope.$apply(function() {
$rootScope.fooRegexp = {};
$rootScope.foo = 'bar';
});
}).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
});
it('should be invalid if entire string does not match pattern', function() {
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
helper.changeInputValueTo('1234');
expect($rootScope.form.test.$error.pattern).not.toBe(true);
expect(inputElm).toBeValid();
helper.changeInputValueTo('123');
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
helper.changeInputValueTo('12345');
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
});
it('should be cope with patterns that start with ^', function() {
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="^\\d{4}">');
helper.changeInputValueTo('1234');
expect($rootScope.form.test.$error.pattern).not.toBe(true);
expect(inputElm).toBeValid();
helper.changeInputValueTo('123');
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
helper.changeInputValueTo('12345');
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
});
it('should be cope with patterns that end with $', function() {
var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}$">');
helper.changeInputValueTo('1234');
expect($rootScope.form.test.$error.pattern).not.toBe(true);
expect(inputElm).toBeValid();
helper.changeInputValueTo('123');
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
helper.changeInputValueTo('12345');
expect($rootScope.form.test.$error.pattern).toBe(true);
expect(inputElm).not.toBeValid();
});
});
describe('minlength', function() {
it('should invalidate values that are shorter than the given minlength', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="3" />');
helper.changeInputValueTo('aa');
expect(inputElm).toBeInvalid();
helper.changeInputValueTo('aaa');
expect(inputElm).toBeValid();
});
it('should listen on ng-minlength when minlength is observed', function() {
var value = 0;
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="min" attr-capture />');
helper.attrs.$observe('minlength', function(v) {
value = int(helper.attrs.minlength);
});
$rootScope.$apply('min = 5');
expect(value).toBe(5);
});
it('should observe the standard minlength attribute and register it as a validator on the model', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="{{ min }}" />');
$rootScope.$apply('min = 10');
helper.changeInputValueTo('12345');
expect(inputElm).toBeInvalid();
expect($rootScope.form.input.$error.minlength).toBe(true);
$rootScope.$apply('min = 5');
expect(inputElm).toBeValid();
expect($rootScope.form.input.$error.minlength).not.toBe(true);
});
it('should validate when the model is initalized as a number', function() {
$rootScope.value = 12345;
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
expect($rootScope.value).toBe(12345);
expect($rootScope.form.input.$error.minlength).toBeUndefined();
});
it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
var ctrl = inputElm.controller('ngModel');
spyOn(ctrl, '$isEmpty').andCallThrough();
ctrl.$parsers.push(function(value) {
return value + '678';
});
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
});
describe('maxlength', function() {
it('should invalidate values that are longer than the given maxlength', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="5" />');
helper.changeInputValueTo('aaaaaaaa');
expect(inputElm).toBeInvalid();
helper.changeInputValueTo('aaa');
expect(inputElm).toBeValid();
});
it('should only accept empty values when maxlength is 0', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="0" />');
helper.changeInputValueTo('');
expect(inputElm).toBeValid();
helper.changeInputValueTo('a');
expect(inputElm).toBeInvalid();
});
it('should accept values of any length when maxlength is negative', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="-1" />');
helper.changeInputValueTo('');
expect(inputElm).toBeValid();
helper.changeInputValueTo('aaaaaaaaaa');
expect(inputElm).toBeValid();
});
it('should accept values of any length when maxlength is non-numeric', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="{{maxlength}}" />');
helper.changeInputValueTo('aaaaaaaaaa');
$rootScope.$apply('maxlength = "5"');
expect(inputElm).toBeInvalid();
$rootScope.$apply('maxlength = "abc"');
expect(inputElm).toBeValid();
$rootScope.$apply('maxlength = ""');
expect(inputElm).toBeValid();
$rootScope.$apply('maxlength = null');
expect(inputElm).toBeValid();
$rootScope.someObj = {};
$rootScope.$apply('maxlength = someObj');
expect(inputElm).toBeValid();
});
it('should listen on ng-maxlength when maxlength is observed', function() {
var value = 0;
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="max" attr-capture />');
helper.attrs.$observe('maxlength', function(v) {
value = int(helper.attrs.maxlength);
});
$rootScope.$apply('max = 10');
expect(value).toBe(10);
});
it('should observe the standard maxlength attribute and register it as a validator on the model', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
$rootScope.$apply('max = 1');
helper.changeInputValueTo('12345');
expect(inputElm).toBeInvalid();
expect($rootScope.form.input.$error.maxlength).toBe(true);
$rootScope.$apply('max = 6');
expect(inputElm).toBeValid();
expect($rootScope.form.input.$error.maxlength).not.toBe(true);
});
it('should assign the correct model after an observed validator became valid', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
$rootScope.$apply('max = 1');
helper.changeInputValueTo('12345');
expect($rootScope.value).toBeUndefined();
$rootScope.$apply('max = 6');
expect($rootScope.value).toBe('12345');
});
it('should assign the correct model after an observed validator became invalid', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
$rootScope.$apply('max = 6');
helper.changeInputValueTo('12345');
expect($rootScope.value).toBe('12345');
$rootScope.$apply('max = 1');
expect($rootScope.value).toBeUndefined();
});
it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
$rootScope.$apply('max = 1');
helper.changeInputValueTo('12345');
expect(inputElm).toBeInvalid();
expect($rootScope.form.input.$error.maxlength).toBe(true);
expect($rootScope.value).toBeUndefined();
$rootScope.$apply('max = 3');
expect(inputElm).toBeInvalid();
expect($rootScope.form.input.$error.maxlength).toBe(true);
expect($rootScope.value).toBeUndefined();
});
it('should not notify if observed maxlength changed, but is still invalid', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" ng-change="ngChangeSpy()" ' +
'maxlength="{{ max }}" />');
$rootScope.$apply('max = 1');
helper.changeInputValueTo('12345');
$rootScope.ngChangeSpy = jasmine.createSpy();
$rootScope.$apply('max = 3');
expect($rootScope.ngChangeSpy).not.toHaveBeenCalled();
});
it('should leave the model untouched when validating before model initialization', function() {
$rootScope.value = '12345';
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
expect($rootScope.value).toBe('12345');
});
it('should validate when the model is initalized as a number', function() {
$rootScope.value = 12345;
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
expect($rootScope.value).toBe(12345);
expect($rootScope.form.input.$error.maxlength).toBeUndefined();
});
it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
var ctrl = inputElm.controller('ngModel');
spyOn(ctrl, '$isEmpty').andCallThrough();
ctrl.$parsers.push(function(value) {
return value + '678';
});
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
});
describe('required', function() {
it('should allow bindings via ngRequired', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-required="required" />');
$rootScope.$apply("required = false");
helper.changeInputValueTo('');
expect(inputElm).toBeValid();
$rootScope.$apply("required = true");
expect(inputElm).toBeInvalid();
$rootScope.$apply("value = 'some'");
expect(inputElm).toBeValid();
helper.changeInputValueTo('');
expect(inputElm).toBeInvalid();
$rootScope.$apply("required = false");
expect(inputElm).toBeValid();
});
it('should invalid initial value with bound required', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" required="{{required}}" />');
$rootScope.$apply('required = true');
expect(inputElm).toBeInvalid();
});
it('should be $invalid but $pristine if not touched', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="name" name="alias" required />');
$rootScope.$apply("name = null");
expect(inputElm).toBeInvalid();
expect(inputElm).toBePristine();
helper.changeInputValueTo('');
expect(inputElm).toBeInvalid();
expect(inputElm).toBeDirty();
});
it('should allow empty string if not required', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="foo" />');
helper.changeInputValueTo('a');
helper.changeInputValueTo('');
expect($rootScope.foo).toBe('');
});
it('should set $invalid when model undefined', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="notDefined" required />');
expect(inputElm).toBeInvalid();
});
it('should consider bad input as an error before any other errors are considered', function() {
var inputElm = helper.compileInput('<input type="text" ng-model="value" required />', { badInput: true });
var ctrl = inputElm.controller('ngModel');
ctrl.$parsers.push(function() {
return undefined;
});
helper.changeInputValueTo('abc123');
expect(ctrl.$error.parse).toBe(true);
expect(inputElm).toHaveClass('ng-invalid-parse');
expect(inputElm).toBeInvalid(); // invalid because of the number validator
});
it('should allow `false` as a valid value when the input type is not "checkbox"', function() {
var inputElm = helper.compileInput('<input type="radio" ng-value="true" ng-model="answer" required />' +
'<input type="radio" ng-value="false" ng-model="answer" required />');
$rootScope.$apply();
expect(inputElm).toBeInvalid();
$rootScope.$apply("answer = true");
expect(inputElm).toBeValid();
$rootScope.$apply("answer = false");
expect(inputElm).toBeValid();
});
it('should validate emptiness against the viewValue', function() {
var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');
var ctrl = inputElm.controller('ngModel');
spyOn(ctrl, '$isEmpty').andCallThrough();
ctrl.$parsers.push(function(value) {
return value + '678';
});
helper.changeInputValueTo('12345');
expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
});
});
});
+33
View File
@@ -92,6 +92,39 @@ describe('Filter: filter', function() {
);
it('should match items with array properties containing one or more matching items', function() {
var items, expr;
items = [
{tags: ['web', 'html', 'css', 'js']},
{tags: ['hybrid', 'html', 'css', 'js', 'ios', 'android']},
{tags: ['mobile', 'ios', 'android']}
];
expr = {tags: 'html'};
expect(filter(items, expr).length).toBe(2);
expect(filter(items, expr)).toEqual([items[0], items[1]]);
items = [
{nums: [1, 345, 12]},
{nums: [0, 46, 78]},
{nums: [123, 4, 67]}
];
expr = {nums: 12};
expect(filter(items, expr).length).toBe(2);
expect(filter(items, expr)).toEqual([items[0], items[2]]);
items = [
{customers: [{name: 'John'}, {name: 'Elena'}, {name: 'Bill'}]},
{customers: [{name: 'Sam'}, {name: 'Klara'}, {name: 'Bill'}]},
{customers: [{name: 'Molli'}, {name: 'Elena'}, {name: 'Lora'}]}
];
expr = {customers: {name: 'Bill'}};
expect(filter(items, expr).length).toBe(2);
expect(filter(items, expr)).toEqual([items[0], items[1]]);
}
);
it('should take object as predicate', function() {
var items = [{first: 'misko', last: 'hevery'},
{first: 'adam', last: 'abrons'}];
+63 -6
View File
@@ -673,6 +673,16 @@ describe('$location', function() {
}));
});
describe('rewrite hashbang url <> html5 url', function() {
beforeEach(initService({html5Mode: true, supportHistory: true}));
beforeEach(inject(initBrowser({url:'http://new.com/#', basePath: '/'})));
it('should not replace browser url if only the empty hash fragment is cleared', inject(function($browser, $location) {
expect($browser.url()).toBe('http://new.com/#');
expect($location.absUrl()).toBe('http://new.com/');
}));
});
describe('wiring', function() {
beforeEach(initService({html5Mode:false,hashPrefix: '!',supportHistory: true}));
@@ -1272,7 +1282,7 @@ describe('$location', function() {
it('should not rewrite links with target="_blank"', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="_blank"'});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="_blank"'});
inject(
initBrowser(),
initLocation(),
@@ -1285,7 +1295,7 @@ describe('$location', function() {
it('should not rewrite links with target specified', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="some-frame"'});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true, attrs: 'target="some-frame"'});
inject(
initBrowser(),
initLocation(),
@@ -1323,7 +1333,7 @@ describe('$location', function() {
});
it ('should not rewrite links when rewriting links is disabled', function() {
it('should not rewrite links when rewriting links is disabled', function() {
configureService({linkHref: 'link?a#b', html5Mode: {enabled: true, rewriteLinks:false}, supportHist: true});
inject(
initBrowser(),
@@ -1480,7 +1490,7 @@ describe('$location', function() {
});
it('should not rewrite when clicked with ctrl pressed', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
@@ -1493,7 +1503,7 @@ describe('$location', function() {
it('should not rewrite when clicked with meta pressed', function() {
configureService({linkHref: '/a?b=c', html5Mode: true, supportHist: true});
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
@@ -1504,6 +1514,53 @@ describe('$location', function() {
);
});
it('should not rewrite when right click pressed', function() {
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
function($browser) {
var rightClick;
if (document.createEvent) {
rightClick = document.createEvent('MouseEvents');
rightClick.initMouseEvent('click', true, true, window, 1, 10, 10, 10, 10, false,
false, false, false, 2, null);
link.dispatchEvent(rightClick);
} else if (document.createEventObject) { // for IE
rightClick = document.createEventObject();
rightClick.type = 'click';
rightClick.cancelBubble = true;
rightClick.detail = 1;
rightClick.screenX = 10;
rightClick.screenY = 10;
rightClick.clientX = 10;
rightClick.clientY = 10;
rightClick.ctrlKey = false;
rightClick.altKey = false;
rightClick.shiftKey = false;
rightClick.metaKey = false;
rightClick.button = 2;
link.fireEvent('onclick', rightClick);
}
expectNoRewrite($browser);
}
);
});
it('should not rewrite when clicked with shift pressed', function() {
configureService({linkHref: 'base/a?b=c', html5Mode: true, supportHist: true});
inject(
initBrowser(),
initLocation(),
function($browser) {
browserTrigger(link, 'click', { keys: ['shift'] });
expectNoRewrite($browser);
}
);
});
it('should not mess up hash urls when clicking on links in hashbang mode', function() {
var base;
@@ -1813,7 +1870,7 @@ describe('$location', function() {
})
);
it ('should fire $locationChangeSuccess event when change from browser location bar',
it('should fire $locationChangeSuccess event when change from browser location bar',
inject(function($log, $location, $browser, $rootScope) {
$rootScope.$apply(); // clear initial $locationChangeStart
+70
View File
@@ -488,6 +488,62 @@ describe('parser', function() {
expect(scope.b).toEqual(234);
});
it('should allow use of locals in the left side of an assignment', inject(function($rootScope) {
$rootScope.a = {};
$rootScope.key = "value";
var localA = {};
//getterFn
$rootScope.$eval('a.value = 1', {a: localA});
expect(localA.value).toBe(1);
$rootScope.$eval('w.a.value = 2', {w: {a: localA}});
expect(localA.value).toBe(2);
//field access
$rootScope.$eval('(a).value = 3', {a: localA});
expect(localA.value).toBe(3);
$rootScope.$eval('{c: {b: a}}.c.b.value = 4', {a: localA});
expect(localA.value).toBe(4);
//object index
$rootScope.$eval('a[key] = 5', {a: localA});
expect(localA.value).toBe(5);
$rootScope.$eval('w.a[key] = 6', {w: {a: localA}});
expect(localA.value).toBe(6);
$rootScope.$eval('{c: {b: a}}.c.b[key] = 7', {a: localA});
expect(localA.value).toBe(7);
//Nothing should have touched the $rootScope.a
expect($rootScope.a.value).toBeUndefined();
}));
it('should allow use of locals in sub expressions of the left side of an assignment', inject(function($rootScope, $parse) {
delete $rootScope.x;
$rootScope.$eval('x[a][b] = true', {a: 'foo', b: 'bar'});
expect($rootScope.x.foo.bar).toBe(true);
delete $rootScope.x;
$rootScope.$eval('x.foo[b] = true', {b: 'bar'});
expect($rootScope.x.foo.bar).toBe(true);
delete $rootScope.x;
$rootScope.$eval('x[a].bar = true', {a: 'foo'});
expect($rootScope.x.foo.bar).toBe(true);
}));
it('should ignore locals beyond the root object of an assignment expression', inject(function($rootScope) {
var a = {};
var locals = {a: a};
$rootScope.b = {a: {value: 123}};
$rootScope.$eval('b.a.value = 1', locals);
expect(a.value).toBeUndefined();
expect($rootScope.b.a.value).toBe(1);
}));
it('should evaluate assignments in ternary operator', function() {
scope.$eval('a = 1 ? 2 : 3');
expect(scope.a).toBe(2);
@@ -799,6 +855,12 @@ describe('parser', function() {
}).toThrowMinErr(
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
'Expression: a.toString.constructor');
expect(function() {
scope.$eval("c.a = 1", {c: Function.prototype.constructor});
}).toThrowMinErr(
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
'Expression: c.a');
});
it('should disallow traversing the Function object in a setter: E02', function() {
@@ -933,6 +995,14 @@ describe('parser', function() {
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
'Expression: foo["bar"]["keys"](foo)');
});
it('should NOT allow access to Object constructor in assignment locals', function() {
expect(function() {
scope.$eval("O.constructor.a = 1", {O: Object});
}).toThrowMinErr(
'$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
'Expression: O.constructor.a');
});
});
describe('Window and $element/node', function() {
+1 -1
View File
@@ -14,7 +14,7 @@ describe('Scope', function() {
it('should expose the constructor', inject(function($rootScope) {
/* jshint -W103 */
if (msie) return;
if (msie < 11) return;
expect($rootScope.__proto__).toBe($rootScope.constructor.prototype);
}));

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