Compare commits

..

119 Commits

Author SHA1 Message Date
Jeff Cross 76df116574 revert: fix(form): ignore properties in $error prototype chain
This reverts commit 31a5b8353a.
2015-01-26 14:50:48 -08:00
Caitlin Potter 440c122556 docs(CHANGELOG.md): add changelog notes for v1.3.11 and v1.4.0-beta.2
Closes #10876
2015-01-26 16:11:54 -05:00
cmsdkfz f7114d0c1c refactor($q): change variable name
Change the variable name to be more accurate describing what it stands for.
2015-01-26 11:04:44 +01:00
Caitlin Potter 2958cd308b 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:39:54 -05:00
Vinti Maheshwari 9f7c5ceba7 chore(gruntFile): ensure build is run before test:modules
Closes #10188
2015-01-25 02:33:09 +00:00
Peter Bacon Darwin 2f32614378 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:35:22 +00:00
Caitlin Potter 8b33de6fd0 fix($location): don't rewrite when link is shift-clicked
Closes #9904
Closes #9906
2015-01-25 00:34:35 +00:00
Peter Bacon Darwin d40749caab 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:41:29 +00:00
Mark Hoffmeyer e13224b2df 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:40:54 +00:00
Marcin Wosinek 36a14e65d1 docs(select): link to ngOptions
Closes #10854
2015-01-24 19:20:16 +01:00
Georgios Kalpakas 3228d3b499 fix(ngPluralize): fix wrong text content when count is null/undefined
When `lastCount` was evaluated to an non-numeric value (e.g. "other") and
`count` was evaluated to `NaN` (e.g. `null`/`undefined`), the text content
would be (wrongly) based on the previous template.
This commits makes sure the text content is updated correctly.

In order to customize the message shown upon `null`/`undefined` one can
specify a `'NaN'` property on the `when` exression object.

Closes #10836

Closes #10841
2015-01-24 14:01:33 +01:00
Jake Harclerode 6d173aeb5d docs($httpBackend): enhance readability
Closes #10852
2015-01-24 12:46:28 +01:00
samilamti d0ceeaa37e 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:37 +00:00
Caitlin Potter 301663e734 fix(openPlunkr): enable cmd+click for us mac users :> 2015-01-23 16:47:19 -05:00
Mike Haggerty dca5fa7b81 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:47:19 -05:00
Rouven Weßling d557875a8d test($rootScope) test the correct setting of the constructor in Internet Explorer 11
Closes #10759
2015-01-23 21:32:35 +00:00
Caitlin Potter b146af1127 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:39:59 -05:00
Pablo Villoslada Puigcerber cea8e75144 fix(filterFilter): throw error if input is not an array
Throw error if filter is not used with an array.

BREAKING CHANGE: 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
2015-01-23 14:21:58 -05:00
Pawel Kozlowski b3a9bd3ae0 fix($templateRequest): cache downloaded templates as strings
Fixes #10630
Closes #10646
2015-01-23 18:43:26 +01:00
Caitlin Potter 31a5b8353a fix(form): ignore properties in $error prototype chain
Closes #10469
Closes #10727
2015-01-23 14:37:17 +00:00
Rouven Weßling 301e7aae24 refactor(Angular): inline the only call to sortedKeys()
Closes #10757
2015-01-21 21:21:44 +01:00
Nick Van Dyck f2e2b31ece docs(ngMessages): fix typo
Closes #10821
2015-01-21 20:18:42 +01:00
Boshen Chen 331cac233f docs(ngInit): fix code block not being displayed in the note section
Closes #10791
2015-01-20 21:34:43 +01:00
Pawel Kozlowski 7e3557e96b docs(CHANGELOG): add changes for 1.3.10 and 1.4.0-beta.1 2015-01-20 20:43:54 +01:00
Pawel Kozlowski d435464c51 feat($http): provide a config object as an argument to header functions
Closes #7235
Closes #10622
2015-01-20 19:42:59 +01:00
Alexandr Subbotin c2031b1e9e 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 10:39:55 -05:00
Pawel Kozlowski d17fbc3862 fix(ngController): allow bound constructor fns as controllers
Fixes #10784
Closes #10790
2015-01-19 20:11:34 +01:00
Matias Niemelä 0db5b21b1d fix($animate): ensure no transitions are applied when an empty inline style object is provided
Closes #10613
Closes #10770
2015-01-19 14:37:58 +00:00
Caitlin Potter c260e73863 fix(ngRepeat): do not sort object keys alphabetically
BREAKING CHANGE:

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
2015-01-19 09:39:08 +00:00
Peter Bacon Darwin e5ad6d6ecd chore(travis): update browsers to the latest version
Update the used browsers to the latest versions available

Closes #10620
2015-01-19 08:22:51 +00:00
Peter Bacon Darwin bd9bc3f828 test(privateMocks): fix for the latest version of Safari 2015-01-19 08:19:20 +00:00
Alexandr Subbotin 2b8baf7e10 refactor($templateRequest): remove repeated decrementation and unnecessary local variable
Closes #10780
2015-01-18 19:53:24 +01:00
Pawel Kozlowski b8e8c5587f docs(ngClass): fix jscs style errors 2015-01-18 19:25:34 +01:00
Evan Spiler 37dd419478 docs(ngClass): fix formatting
Closes #10793
2015-01-18 19:11:17 +01:00
Robin Andersson 8a433f3cc5 docs($cookie): clarify return value if no cookie exist
Closes #10794
2015-01-18 19:09:10 +01:00
thorn0 1476810a2c docs(angular.bootstrap): passed fns are called on config stage
Closes #10789
2015-01-17 18:10:02 +01:00
Martin Staffa 833ea05abf 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:43:53 +01:00
Martin Staffa ca5fcc6f7a refactor(ngModel): clarify the arguments of $setValidity 2015-01-16 21:43:53 +01:00
Martin Staffa 2408f2ded5 fix(form): clean up success state of controls when they are removed
Fixes #10509
2015-01-16 21:43:53 +01:00
Martin Staffa 1bf1a6203c docs(migration): in 1.3, global controllers are disabled by default
Closes #10775
2015-01-16 21:25:39 +01:00
Sekib Omazic b7117afa2f docs($rootScope.Scope): Simple typo
Closes 10767
2015-01-16 21:11:57 +01:00
Sekib Omazic 3ae79c0105 docs(ngInclude): Typo fixed
Typo fixed
2015-01-16 21:09:17 +01:00
Sekib Omazic 2b64f6e318 docs(ngMessages): --typos.length
Merci~

Closes #10769
2015-01-15 15:46:13 -05:00
Caitlin Potter 23c8a90d22 fix($compile): support class directives on SVG elements
Closes #10736
Closes #10756
2015-01-15 13:58:57 -05:00
Marcin Wosinek 0504395c14 docs(design): highlight source button when focused 2015-01-15 13:57:07 +00:00
Kiran Rao 3831de8a05 docs(CONTRIBUTING): add colons for consistent styling
Closes #10744
2015-01-15 13:54:25 +00:00
campersau 40abdaf407 docs($templateRequest): remove duplicate "the" in return description
Introduced with: 1e5e527c84

Closes #10714
2015-01-15 13:51:58 +00:00
Martin Mouterde 3422cbac80 docs(date): fix milliseconds syntax description
There is no need to prefix 'sss' with ',' or '.'.

Closes #10680
2015-01-15 13:43:25 +00:00
anyong 08035545ed 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:14:56 +00:00
Kok-Hou Chia 9580bc2c2e docs(tutorial/7): correct typos
Closes #10587
2015-01-15 13:12:36 +00:00
Tyler Morgan 5a9dde1c27 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:03:12 +00:00
Olivier Giulieri 3a17799098 docs(css): fix position and size of Table of Contents "close" button
Closes #10555
2015-01-15 12:53:04 +00:00
Peter Bacon Darwin 1a7e9de8d8 chore(version-info): use branchPattern where we are replacing * 2015-01-14 20:44:32 +00:00
Peter Bacon Darwin 299b3e7e01 chore(release-scripts): split BranchPattern from BranchVersion
The release scripts need the BranchPattern to be of the form: 1.4.* so that
they can match the version using Regex.

The doc gen scripts need a SemVer pattern that will match beta releases.
The convention is that 1.4.x is not satisfied by 1.4.0.beta.0
2015-01-14 20:36:17 +00:00
Peter Bacon Darwin 54cae0f1d0 chore(package.json): increment the branch pattern to 1.4 2015-01-14 18:18:46 +00:00
Jonathan Gruber 4af7cdaf4d docs(tutorial/step_10): Added missing semicolon
Added a missing semicolon in definition of $scope.setImage.

Closes #10752
2015-01-14 09:41:49 -05:00
Caitlin Potter 593b18c66a revert: chore(npm): Make require()-able as part of publish script
This reverts commit babc20b43d.

(We wanted to get some feedback before doin this)
2015-01-13 14:25:59 -05:00
Peter Bacon Darwin f2e1a930aa docs(CHANGELOG): add changes for 1.3.9 and 1.4.0-beta.0 2015-01-13 00:58:55 +00:00
Ben Clinkinbeard babc20b43d 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:08:45 -05:00
Peter Bacon Darwin ba90261b75 fix(ngOptions): support one-time binding on the option values
Utilize the $watchDelegate on the watcher used to detect changes to the labels.

Closes #10687
Closes #10694
2015-01-12 19:48:42 +00:00
Peter Bacon Darwin fc21db8a15 fix(ngOptions): prevent infinite digest if track by expression is stable
Closes #9464
2015-01-12 19:48:41 +00:00
Peter Bacon Darwin b4bdec35cb refact(ngOptions): specialize readValue and writeValue based on multiple attribute 2015-01-12 19:48:41 +00:00
Peter Bacon Darwin 933591d69c fix(ngOptions): update model if selected option is removed
Closes #7736
2015-01-12 19:48:41 +00:00
Peter Bacon Darwin cf9331ac66 style(ngOptionsSpec): add extra newline for better formatting 2015-01-12 19:48:41 +00:00
Peter Bacon Darwin 02977c5bab test(ngOptions): should not insert a blank option if one of the options maps to null
Closes #7605
2015-01-12 19:48:41 +00:00
Peter Bacon Darwin 9f5ac048d7 test(ngOptions): should place non-grouped items in the list where they appear
Closes #10531
2015-01-12 19:48:41 +00:00
Peter Bacon Darwin 408f89d8e6 refact(ngOptions): move into its own file
Since select is not aware of ngOptions, it makes sense to move it into its
own file for more easy maintenance.
2015-01-12 19:48:41 +00:00
Peter Bacon Darwin 7fda214c4f fix(ngOptions): ensure that the correct option is selected when options are loaded async
**Major reworking of select and ngOptions**:

* The `SelectController` is now used as an abstraction for the `select` and `ngOptions` directives
to override to get their desired behaviour
* The `select` directive is completely oblivious to the ngOptions directive now - the `ngOptions`
directive could be deleted without having to make any changes to the `select` directive.
* Select related directives (single/multiple/ngOptions) can provide specific versions of
`SelectController.writeValue` and `SelectController.readValue`, which are responsible for getting
the `$viewValue` in or out of the actual `<select>` element and its `<option>` children.

BREAKING CHANGE:

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.

BREAKING CHANGE:

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.

Closes #8019
Closes #9714
Closes #10639
2015-01-12 19:48:41 +00:00
Julie Ralph eb6cb785df chore(testing): bump protractor to version 1.6.0 2015-01-12 11:46:46 -08:00
Roman Konstantinovich aa798f1236 fix($location): right button click in firefox
When user click right mouse button on links in firefox, browser goes to
link. See http://jsfiddle.net/kromxr/76fKM/12/

Closes #7984
2015-01-12 17:13:54 +01:00
vasileorza 5a60302389 feat($timeout): allow fn to be an optional parameter
Closes #9176
Close #9723
2015-01-12 11:38:34 +00:00
Petr Peller 034fade3e8 docs(error/nobase): Added trailing slash for base path
Trailing slash seems to be necessary, otherwise `$routeProvider` does not match routes correctly. Following is not matched:

URL http://www.example.com/b/foo/1234
`<base href="/b/foo">`
```
$routeProvider.when('/:id', {
            templateUrl: '/view/path.html',
            controller: 'MyCtrl',
            reloadOnSearch: false
        });
```
2015-01-11 20:27:12 +01:00
Pawel Kozlowski e24f22bdb1 fix($templateRequest): propagate HTTP status on failed requests
Fixes #10514
Closes #10628
2015-01-11 10:56:19 +01:00
Jesse Palmer 371c1e19d8 docs(app): increment copyright year
Closes #10712
2015-01-11 10:54:42 +01:00
Jason Deppen b5e00cf615 docs(ngModel): from makes more sense than the other thing
Not sure if this is a worthy change but it confused me when I read it. It is worthy, thank you for submitting this! Cheers!

In practice, different from is by far the most common of the three, in both British and American English:
http://www.oxforddictionaries.com/us/words/different-from-than-or-to

Closes #10710
2015-01-11 01:22:06 -05:00
Andrew Joslin 5765061652 docs(ngMessages): fix typo on messages
The css class `.ng-inactive` applies when there is no message present
2015-01-10 17:59:59 +01:00
Leonardo Braga b146cae02c refactor(minErr): cleanup the generation of the error message
Removes a "magic number" used multiple times in the code
Removes unnecessary variables "arg" and "prefix"
Removed a condition within the "for" loop that generates query string parameters
2015-01-10 17:25:58 +01:00
Julie Ralph 3353afbb59 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:10:29 -08:00
Julie Ralph 40cb57c8f6 chore(travis): make browserstack unit tests allowed failures 2015-01-09 10:23:31 -08:00
Peter Bacon Darwin f06f28e018 revert: refact(): no need to trim empty hash from $location.absUrl()
This reverts commit f3b088a4e4.

The commit didn't take into account that IE9 may actually return empty
hash URLs for $location.absUrl().
2015-01-09 13:24:05 +00:00
Peter Bacon Darwin f3b088a4e4 refact(): no need to trim empty hash from $location.absUrl()
Only `$browser.url()` ever contains an empty hash fragment so that is the
only call that needs to be trimmed.

Closes #10515
2015-01-08 20:12:00 +00:00
Lucas Galfaso ef1a9d2cda chore(ngModelOptions): remove reference to angular.copy
Replaced a reference to `angular.copy` with just `copy`
2015-01-08 20:02:28 +01:00
Shahar Talmi 9c9c6b3fe4 fix(ngModelOptions): allow sharing options between multiple inputs
Closes #10667
2015-01-08 12:36:54 +01:00
quentin 51d6774286 feat($filter): Display Infinity symbol when number is Infinity
Infinity is a value and should not be treated as an empty string

Closes #10421
2015-01-08 12:14:48 +01:00
Leonardo Braga e079111b33 fix(ngChecked): ensure that ngChecked doesn't interfere with ngModel
Corrects an issue which occurs when an element's ng-modeol and ng-checked attributes have the same
value.

Closes #10662
Closes #10664
2015-01-07 20:45:41 -05:00
Lucas Galfaso e1132f53b0 fix(ngClass): handle multi-class definitions as an element of an array
Handles multi-class definition as an element of an array

Closes #8578
Closes #10651
2015-01-07 20:36:46 +01:00
Karl ab4b632dbf refactor(ngMessages): remove unused function argument
Closes #10652
2015-01-07 19:56:00 +01:00
eemmosi 1b704071c8 docs(tutorial/Routing): improve wording
Closes #10668
2015-01-07 19:53:11 +01:00
Peter Bacon Darwin 647d93338f chore(benchmark): add ngOptions benchmark 2015-01-07 13:35:25 +00:00
Pawel Kozlowski 1334b8c832 fix(dateFilter): ignore invalid dates
Fixes #10640
2015-01-06 23:18:32 +01:00
Uri Goldshtein d2a9a163fb docs(guide/index): add angular-easyfb with Facebook login to login libraries
Merci~

Closes #5792
2015-01-06 13:40:59 -05:00
Bryce Hanscomb e24d968276 style(ngRoute): move comment inside relevant function
This is a functional workaround for https://github.com/6to5/6to5/issues/376
And makes the comment code-style more consistent with line 143 and line 463.
2015-01-05 21:10:22 +01:00
Andrey Pushkarev a01ce6b81c 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:14:23 -05:00
Marc Laval c66b4b6a13 fix(ngPluralize): generate a warning when using a not defined rule
When using `ng-pluralize` and a rule is missing, then generate a warning

Fix #10207
2015-01-02 12:53:02 +01:00
Leonardo Braga 66ceecc295 refact($SnifferProvider): use bodyStyle var instead of document.body.style
Closes #10602
2014-12-31 15:09:16 +01:00
Pawel Kozlowski 349742b3f0 test($http): remove unused inject() calls
Closes #10611
2014-12-31 14:50:30 +01:00
Rus1 2ff7edfdd1 docs(ngInclude): replace <tt> with <code>
Using obsolete <tt> HTML tag may not be good for Angular examples

Closes #10594
2014-12-30 15:47:59 -05:00
Raphael Luba 1e5e527c84 docs($templateRequest): fix "returns" description to match code
Closes #10603
2014-12-30 19:01:20 +01:00
Mike Sidorov 1c76bf7e94 chore(*): add .gitattributes with new lines configuration
Closes #10431
Closes #10605
2014-12-30 18:53:50 +01:00
Daniel Tsui 6018f5da3f docs(misc/FAQ): grammatical improvements
-Non-idiomatic use of an expression "from the ground up".
-Missing commas.

Closes #10593
2014-12-29 21:19:04 +01:00
袴田 俊輔 3616b9b07c refactor(ngScenario): use Date.now() for get current Time
Closes #10579
2014-12-29 21:15:53 +01:00
Lucas Galfaso d224fe8172 docs(input): fix typo
Fix a typo on an example from the `input` directive
2014-12-28 20:57:43 +01:00
Lucas Galfaso e9bf93d510 refactor(*): rename internal function int to toInt
Renamed the internal function `int` to `toInt` as `int` is a reserved word

Closes #7768
2014-12-28 20:56:05 +01:00
mjfroehlich 2e721a7914 docs(guide/modules): fix minor typos
Closes #10584
2014-12-28 18:50:13 +01:00
Chris Schmitz 1eb6036d29 docs($compile) fix a typo
Remove unnecessary 'and' in $compile docs.

Closes #10582
2014-12-28 18:48:40 +01:00
OKNoah 4836dacae6 docs($http): fix markdown formatting
Closes #10571
2014-12-28 18:42:01 +01:00
Lucas Galfaso 0e2ac3cd70 chore($cache): do not add entry to LRU hash when value is undefined
When adding entries to the cache and the `value` is `undefined`, no
entry should be added to the `lruHash`
2014-12-28 16:59:57 +01:00
Peter Bacon Darwin 0f9fd2f642 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:19:39 +00:00
Peter Bacon Darwin 3e42b22b0e 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:07:07 +00:00
David Souther deb3cb4dae 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:16:51 +00:00
David Souther b43fa3bb30 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 16:28:08 +00:00
gokulkrishh 521c12c265 docs(guide/*): spelling/grammar improvements
Closes #10552
2014-12-22 10:43:55 -05:00
Caitlin Potter 7f5051bb2a docs($rootScope): remove erroneous closing parenthesis
Closes #10549
2014-12-22 08:33:10 -05:00
Kevin Primat 25623b709f 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:24:27 -05:00
Olivier Giulieri e4f23c4d25 docs(guide): fix spaces
Closes #10539
2014-12-22 01:07:46 +00:00
gdi2290 8928d02345 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:35:38 +00:00
Dan Cancro 5bb2636aac 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:02 +00:00
Robert Haritonov c95e38c603 docs(tutorial/12): fix path to jquery in bower
Closes #10504
2014-12-22 00:12:45 +00:00
Peter Bacon Darwin a3c3bf3332 feat(limitTo): ignore limit when invalid
BREAKING CHANGE: limitTo changed behavior when limit value is invalid.
Instead of returning empty object/array it returns unchanged input.

Closes #10510
2014-12-21 10:03:43 +00:00
107 changed files with 4030 additions and 3888 deletions
+5
View File
@@ -0,0 +1,5 @@
# Auto detect text files and perform LF normalization
* text=auto
# JS files must always use LF for tools to work
*.js eol=lf
+2 -9
View File
@@ -1,7 +1,6 @@
{
"excludeFiles": ["src/ngLocale/**"],
"disallowKeywords": ["with"],
"disallowKeywordsOnNewLine": ["else"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineStrings": true,
"disallowNewlineBeforeBlockStatements": true,
@@ -12,7 +11,6 @@
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInCallExpression": true,
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
@@ -20,11 +18,6 @@
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"requireSpaceBeforeKeywords": [
"else",
"while",
"catch"
],
"disallowSpacesInsideParentheses": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
@@ -40,9 +33,9 @@
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInForStatement": true,
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"validateLineBreaks": "LF"
"validateLineBreaks": "LF",
"validateParameterSeparator": ", "
}
+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
+6 -99
View File
@@ -1,102 +1,3 @@
<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)
@@ -112,6 +13,9 @@ is marked as optional and the attribute is not specified, no function will be ad
- **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))
- **form:** ignore properties in $error prototype chain
([31a5b835](https://github.com/angular/angular.js/commit/31a5b8353ae5f1a5cb283322829880995e877833),
[#10469](https://github.com/angular/angular.js/issues/10469), [#10727](https://github.com/angular/angular.js/issues/10727))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([2958cd30](https://github.com/angular/angular.js/commit/2958cd308b5ebaf223a3e5df3fb5bf0f23408447),
@@ -143,6 +47,9 @@ Closes #10352
- **$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))
- **form:** ignore properties in $error prototype chain
([adf91fe6](https://github.com/angular/angular.js/commit/adf91fe6ee77a84e8159c9a95e36f65276fe67bd),
[#10469](https://github.com/angular/angular.js/issues/10469), [#10727](https://github.com/angular/angular.js/issues/10727))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([779e3f6b](https://github.com/angular/angular.js/commit/779e3f6b5f8d2550e758cb0c5f64187ba8e00e29),
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License
Copyright (c) 2010-2014 Google, Inc. http://angularjs.org
Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1
View File
@@ -65,6 +65,7 @@ var angularFiles = {
'src/ng/directive/ngList.js',
'src/ng/directive/ngModel.js',
'src/ng/directive/ngNonBindable.js',
'src/ng/directive/ngOptions.js',
'src/ng/directive/ngPluralize.js',
'src/ng/directive/ngRepeat.js',
'src/ng/directive/ngShowHide.js',
+95
View File
@@ -0,0 +1,95 @@
"use strict";
/* globals angular, benchmarkSteps */
var app = angular.module('ngOptionsBenchmark', []);
app.config(function($compileProvider) {
if ($compileProvider.debugInfoEnabled) {
$compileProvider.debugInfoEnabled(false);
}
});
app.controller('DataController', function($scope, $element) {
$scope.items = [];
$scope.count = 10000;
function changeOptions() {
$scope.items = [];
for (var i = 0; i < $scope.count; ++i) {
$scope.items.push({
id: i,
label: 'item-' + i,
group: 'group-' + i % 100
});
}
}
var selectElement = $element.find('select');
console.log(selectElement);
benchmarkSteps.push({
name: 'add-options',
fn: function() {
$scope.$apply(function() {
$scope.count = 10000;
changeOptions();
});
}
});
benchmarkSteps.push({
name: 'set-model-1',
fn: function() {
$scope.$apply(function() {
$scope.x = $scope.items[1000];
});
}
});
benchmarkSteps.push({
name: 'set-model-2',
fn: function() {
$scope.$apply(function() {
$scope.x = $scope.items[10];
});
}
});
benchmarkSteps.push({
name: 'remove-options',
fn: function() {
$scope.count = 100;
changeOptions();
}
});
benchmarkSteps.push({
name: 'add-options',
fn: function() {
$scope.$apply(function() {
$scope.count = 10000;
changeOptions();
});
}
});
benchmarkSteps.push({
name: 'set-view-1',
fn: function() {
selectElement.val('2000');
selectElement.triggerHandler('change');
}
});
benchmarkSteps.push({
name: 'set-view-2',
fn: function() {
selectElement.val('1000');
selectElement.triggerHandler('change');
}
});
});
+11
View File
@@ -0,0 +1,11 @@
module.exports = function(config) {
config.set({
scripts: [ {
id: 'angular',
src: '/build/angular.js'
},
{
src: 'app.js',
}]
});
};
+10
View File
@@ -0,0 +1,10 @@
<div ng-app="ngOptionsBenchmark" ng-cloak>
<div ng-controller="DataController">
<div class="container-fluid">
<p>
Tests the execution of ng-options for rendering during model and option updates.
</p>
<select ng-model="x" ng-options="a as a.label group by a.group for a in items track by a.id"></select>
</div>
</div>
</div>
+1 -2
View File
@@ -415,7 +415,7 @@ iframe.example {
.main-body-grid .side-navigation {
position:relative;
padding-bottom:120px;
padding-bottom:50px;
}
.main-body-grid .side-navigation.ng-hide {
@@ -631,7 +631,6 @@ ul.events > li {
}
.main-body-grid .side-navigation {
display:block!important;
padding-bottom:50px;
}
.main-body-grid .side-navigation.ng-hide {
display:none!important;
@@ -220,7 +220,7 @@
<p class="pull-right"><a back-to-top>Back to top</a></p>
<p>
Super-powered by Google ©2010-2014
Super-powered by Google ©2010-2015
( <a id="version"
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
ng-bind-template="v{{version}}">
@@ -1,46 +0,0 @@
@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 @@ URL of the subcontext:
```html
<head>
<base href="/subapp">
<base href="/subapp/">
...
</head>
```
+8
View File
@@ -0,0 +1,8 @@
@ngdoc error
@name filter:notarray
@fullName Not an array
@description
This error occurs when {@link ng.filter filter} is not used with an array.
Filter must be used with an array so a subset of items can be returned.
The array can be initialized asynchronously so null or undefined won't throw this error.
+5 -9
View File
@@ -37,7 +37,6 @@ 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>
@@ -210,14 +209,10 @@ 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="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.
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`.
<h3>Example</h3>
```html
@@ -228,6 +223,7 @@ 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>
+5 -6
View File
@@ -6,12 +6,11 @@
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
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.
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.
# Simple form
+1 -1
View File
@@ -102,7 +102,7 @@ This is a short list of libraries with specific support and documentation for wo
## Learning Resources
###Books
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [AngularJS](http://www.amazon.com/AngularJS-Brad-Green/dp/1449344852) 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
+2 -2
View File
@@ -197,14 +197,14 @@ Then Angular applies configuration blocks in the same order they were registered
## Run Blocks
Run blocks are the closest thing in Angular to the main method. A run block is the code which
needs to run to kickstart the application. It is executed after all of the service have been
needs to run to kickstart the application. It is executed after all of the services have been
configured and the injector has been created. Run blocks typically contain code which is hard
to unit-test, and for this reason should be declared in isolated modules, so that they can be
ignored in the unit-tests.
## Dependencies
Modules can list other modules as their dependencies. Depending on a module implies that required
Modules can list other modules as their dependencies. Depending on a module implies that the required
module needs to be loaded before the requiring module is loaded. In other words the configuration
blocks of the required modules execute before the configuration blocks of the requiring module.
The same is true for the run blocks. Each module can only be loaded once, even if multiple other
+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 binding and scopes to DOM nodes,
and adds CSS classes to data-bound elements:
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:
- As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-binding` are attached to the corresponding element.
As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-class` is 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()`.
+6 -6
View File
@@ -33,7 +33,7 @@ templating systems.
### Do I need to worry about security holes in AngularJS?
Like any other technology, AngularJS is not impervious to attack. Angular does, however, provide
built-in protection from basic security holes including cross-site scripting and HTML injection
built-in protection from basic security holes, including cross-site scripting and HTML injection
attacks. AngularJS does round-trip escaping on all strings for you and even offers XSRF protection
for server-side communication.
@@ -52,7 +52,7 @@ Yes. See instructions in {@link downloading}.
We run our extensive test suite against the following browsers: Safari, Chrome, Firefox, Opera 15,
IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari). See {@link guide/ie Internet
Explorer Compatibility} for more details in supporting legacy IE browsers.
Explorer Compatibility} for more details on supporting legacy IE browsers.
### What's Angular's performance like?
@@ -61,8 +61,8 @@ The startup time heavily depends on your network connection, state of the cache,
available hardware, but typically we measure bootstrap time in tens or hundreds of milliseconds.
The runtime performance will vary depending on the number and complexity of bindings on the page
as well as the speed of your backend (for apps that fetch data from the backend). Just for an
illustration we typically build snappy apps with hundreds or thousands of active bindings.
as well as the speed of your backend (for apps that fetch data from the backend). For an
illustration, we typically build snappy apps with hundreds or thousands of active bindings.
### How big is the angular.js file that I need to include?
@@ -88,7 +88,7 @@ but we don't guarantee that.
### What is testability like in Angular?
Very testable and designed this way from ground up. It has an integrated dependency injection
Very testable and designed this way from the ground up. It has an integrated dependency injection
framework, provides mocks for many heavy dependencies (server-side communication). See
{@link ngMock} for details.
@@ -189,7 +189,7 @@ Then whenever a value on a scope changes, all `$watch`es observing that element
Sometimes, usually when you're writing a custom directive, you will have to define your own `$watch` on a scope value to make the directive react to changes.
On the flip side, sometimes you change a scope value in some code but the app doesn't react to it.
On the flip side, sometimes you change a scope value in some code, but the app doesn't react to it.
Angular checks for scope variable changes after pieces of your code have finished running; for example, when `ng-click` calls a function on your scope, Angular will check for changes and react.
However, some code is outside of Angular and you'll have to call `scope.$apply()` yourself to trigger the update.
This is most commonly seen in event handlers in custom directives.
+1 -1
View File
@@ -11,7 +11,7 @@ multiple views by adding routing, using an Angular module called 'ngRoute'.
* When you now navigate to `app/index.html`, you are redirected to `app/index.html/#/phones`
and the phone list appears in the browser.
* When you click on a phone link the url changes to one specific to that phone and the stub of a
* When you click on a phone link, the url changes to that specific phone and the stub of a
phone detail page is displayed.
<div doc-tutorial-reset="7"></div>
+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]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
[bower]: http://bower.io/
+11 -7
View File
@@ -2912,12 +2912,16 @@ goog.i18n.DateTimeSymbols_my = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -2941,7 +2945,7 @@ goog.i18n.DateTimeSymbols_my = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
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,
+11 -7
View File
@@ -14493,12 +14493,16 @@ goog.i18n.DateTimeSymbols_my_MM = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -14522,7 +14526,7 @@ goog.i18n.DateTimeSymbols_my_MM = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
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: 'EUR'
DEF_CURRENCY_CODE: 'LTL'
};
+7 -3
View File
@@ -83,7 +83,7 @@ var getTaggedVersion = function() {
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
version.codeName = getCodeName(tag);
version.full = version.version;
version.branch = 'v' + currentPackage.branchVersion.replace('*', 'x');
version.branch = 'v' + currentPackage.branchPattern.replace('*', 'x');
return version;
}
}
@@ -130,13 +130,17 @@ var getCdnVersion = function() {
return semver.satisfies(tag, currentPackage.branchVersion);
})
.reverse()
.tap(function(versions) {
console.log(versions);
})
.reduce(function(cdnVersion, version) {
if (!cdnVersion) {
// Note: need to use shell.exec and curl here
// as version-infos returns its result synchronously...
var cdnResult = shell.exec('curl http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js '+
'--head --write-out "%{http_code}" -o /dev/null -silent',
{silent: true});
{silent: false});
console.log('http://ajax.googleapis.com/ajax/libs/angularjs/'+version+'/angular.min.js');
if ( cdnResult.code === 0 ) {
var statusCode = cdnResult.output.trim();
if (statusCode === '200') {
@@ -161,7 +165,7 @@ var getSnapshotVersion = function() {
if ( !version ) {
// a snapshot version before the first tag on the branch
version = semver(currentPackage.branchVersion.replace('*','0-beta.1'));
version = semver(currentPackage.branchPattern.replace('*','0-beta.1'));
}
// We need to clone to ensure that we are not modifying another version
+51 -75
View File
@@ -1395,66 +1395,66 @@
}
},
"dgeni-packages": {
"version": "0.10.8",
"version": "0.10.7",
"dependencies": {
"catharsis": {
"version": "0.7.1"
},
"change-case": {
"version": "2.2.0",
"version": "2.1.5",
"dependencies": {
"camel-case": {
"version": "1.1.1"
"version": "1.0.2"
},
"constant-case": {
"version": "1.1.0"
"version": "1.0.0"
},
"dot-case": {
"version": "1.1.0"
"version": "1.0.1"
},
"is-lower-case": {
"version": "1.1.0"
"version": "1.0.0"
},
"is-upper-case": {
"version": "1.1.0"
"version": "1.0.1"
},
"lower-case": {
"version": "1.1.1"
"version": "1.0.2"
},
"param-case": {
"version": "1.1.0"
"version": "1.0.1"
},
"pascal-case": {
"version": "1.1.0"
"version": "1.0.0"
},
"path-case": {
"version": "1.1.0"
"version": "1.0.1"
},
"sentence-case": {
"version": "1.1.1"
"version": "1.1.0"
},
"snake-case": {
"version": "1.1.0"
"version": "1.0.1"
},
"swap-case": {
"version": "1.1.0"
"version": "1.0.2"
},
"title-case": {
"version": "1.1.0"
"version": "1.0.1"
},
"upper-case": {
"version": "1.1.1"
"version": "1.0.3"
},
"upper-case-first": {
"version": "1.1.0"
"version": "1.0.1"
}
}
},
"esprima": {
"version": "1.2.3"
"version": "1.2.2"
},
"estraverse": {
"version": "1.9.1"
"version": "1.7.1"
},
"glob": {
"version": "3.2.11",
@@ -1626,7 +1626,7 @@
}
},
"node-uuid": {
"version": "1.4.2"
"version": "1.4.1"
},
"cookie-jar": {
"version": "0.2.0"
@@ -2342,32 +2342,26 @@
},
"grunt-jasmine-node": {
"version": "0.1.0",
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7",
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
},
"grunt-jscs": {
"version": "1.2.0",
"version": "0.7.1",
"dependencies": {
"hooker": {
"version": "0.2.3"
},
"jscs": {
"version": "1.10.0",
"version": "1.6.2",
"dependencies": {
"colors": {
"version": "1.0.3"
"version": "0.6.2"
},
"commander": {
"version": "2.5.1"
"version": "2.3.0"
},
"esprima": {
"version": "1.2.3"
},
"esprima-harmony-jscs": {
"version": "1.1.0-regex-token-fix"
},
"estraverse": {
"version": "1.9.1"
"version": "1.2.2"
},
"exit": {
"version": "0.1.2"
@@ -2376,22 +2370,11 @@
"version": "4.0.6",
"dependencies": {
"graceful-fs": {
"version": "3.0.5"
"version": "3.0.4"
},
"inherits": {
"version": "2.0.1"
},
"minimatch": {
"version": "1.0.0",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
}
},
"once": {
"version": "1.3.1",
"dependencies": {
@@ -2403,61 +2386,54 @@
}
},
"minimatch": {
"version": "2.0.1",
"version": "1.0.0",
"dependencies": {
"brace-expansion": {
"version": "1.1.0",
"dependencies": {
"balanced-match": {
"version": "0.2.0"
},
"concat-map": {
"version": "0.0.1"
}
}
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
}
},
"strip-json-comments": {
"version": "1.0.2"
"version": "1.0.1"
},
"vow-fs": {
"version": "0.3.4",
"version": "0.3.2",
"dependencies": {
"node-uuid": {
"version": "1.4.2"
"version": "1.4.0"
},
"vow": {
"version": "0.4.4"
},
"vow-queue": {
"version": "0.4.1"
"version": "0.3.1"
},
"glob": {
"version": "4.3.5",
"version": "3.2.8",
"dependencies": {
"inflight": {
"version": "1.0.4",
"minimatch": {
"version": "0.2.14",
"dependencies": {
"wrappy": {
"version": "1.0.1"
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
}
},
"inherits": {
"version": "2.0.1"
},
"once": {
"version": "1.3.1",
"dependencies": {
"wrappy": {
"version": "1.0.1"
}
}
}
}
}
}
},
"xmlbuilder": {
"version": "2.4.6",
"version": "2.4.4",
"dependencies": {
"lodash-node": {
"version": "2.4.1"
@@ -2465,12 +2441,12 @@
}
},
"supports-color": {
"version": "1.2.0"
"version": "1.1.0"
}
}
},
"vow": {
"version": "0.4.8"
"version": "0.4.5"
}
}
},
+3 -2
View File
@@ -1,6 +1,7 @@
{
"name": "angularjs",
"branchVersion": "1.3.*",
"branchVersion": "^1.4.0-beta.0",
"branchPattern": "1.4.*",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
@@ -24,7 +25,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": "~1.2.0",
"grunt-jscs": "~0.7.1",
"grunt-merge-conflict": "~0.0.1",
"grunt-shell": "~1.1.1",
"gulp": "~3.8.0",
+1 -1
View File
@@ -17,7 +17,7 @@ ARG_DEFS=(
)
function checkVersionNumber() {
BRANCH_PATTERN=$(readJsonProp "package.json" "branchVersion")
BRANCH_PATTERN=$(readJsonProp "package.json" "branchPattern")
if [[ $VERSION_NUMBER != $BRANCH_PATTERN ]]; then
echo "version-number needs to match $BRANCH_PATTERN on this branch"
usage
+1 -2
View File
@@ -28,13 +28,12 @@
"manualUppercase": false,
"isArrayLike": false,
"forEach": false,
"sortedKeys": false,
"forEachSorted": false,
"reverseParams": false,
"nextUid": false,
"setHashKey": false,
"extend": false,
"int": false,
"toInt": false,
"inherit": false,
"noop": false,
"identity": false,
+7 -15
View File
@@ -22,13 +22,12 @@
nodeName_: true,
isArrayLike: true,
forEach: true,
sortedKeys: true,
forEachSorted: true,
reverseParams: true,
nextUid: true,
setHashKey: true,
extend: true,
int: true,
toInt: true,
inherit: true,
noop: true,
identity: true,
@@ -272,12 +271,8 @@ function forEach(obj, iterator, context) {
return obj;
}
function sortedKeys(obj) {
return Object.keys(obj).sort();
}
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj);
var keys = Object.keys(obj).sort();
for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
@@ -317,7 +312,8 @@ function nextUid() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
} else {
}
else {
delete obj.$$hashKey;
}
}
@@ -356,7 +352,7 @@ function extend(dst) {
return dst;
}
function int(str) {
function toInt(str) {
return parseInt(str, 10);
}
@@ -626,7 +622,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;
}
@@ -1407,12 +1403,8 @@ function bootstrap(element, modules, config) {
forEach(extraModules, function(module) {
modules.push(module);
});
return doBootstrap();
doBootstrap();
};
if (isFunction(angular.resumeDeferredBootstrap)) {
angular.resumeDeferredBootstrap();
}
}
/**
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2014 Google, Inc. http://angularjs.org
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {
+2 -2
View File
@@ -800,7 +800,7 @@ function createInjector(modulesToLoad, strictDi) {
}
var args = [],
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
$inject = annotate(fn, strictDi, serviceName),
length, i,
key;
@@ -839,7 +839,7 @@ function createInjector(modulesToLoad, strictDi) {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
annotate: annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2014 Google, Inc. http://angularjs.org
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
'use strict';
+18 -13
View File
@@ -33,28 +33,33 @@
function minErr(module, ErrorConstructor) {
ErrorConstructor = ErrorConstructor || Error;
return function() {
var code = arguments[0],
prefix = '[' + (module ? module + ':' : '') + code + '] ',
template = arguments[1],
templateArgs = arguments,
var SKIP_INDEXES = 2;
message, i;
var templateArgs = arguments,
code = templateArgs[0],
message = '[' + (module ? module + ':' : '') + code + '] ',
template = templateArgs[1],
paramPrefix, i;
message = prefix + template.replace(/\{\d+\}/g, function(match) {
var index = +match.slice(1, -1), arg;
message += template.replace(/\{\d+\}/g, function(match) {
var index = +match.slice(1, -1),
shiftedIndex = index + SKIP_INDEXES;
if (index + 2 < templateArgs.length) {
return toDebugString(templateArgs[index + 2]);
if (shiftedIndex < templateArgs.length) {
return toDebugString(templateArgs[shiftedIndex]);
}
return match;
});
message = message + '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
message += '\nhttp://errors.angularjs.org/"NG_VERSION_FULL"/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
encodeURIComponent(toDebugString(arguments[i]));
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' +
encodeURIComponent(toDebugString(templateArgs[i]));
}
return new ErrorConstructor(message);
};
}
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2014 Google, Inc. http://angularjs.org
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
+1 -1
View File
@@ -159,13 +159,13 @@ function $CacheFactoryProvider() {
* @returns {*} the value stored.
*/
put: function(key, value) {
if (isUndefined(value)) return;
if (capacity < Number.MAX_VALUE) {
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
refresh(lruEntry);
}
if (isUndefined(value)) return;
if (!(key in data)) size++;
data[key] = value;
+3 -2
View File
@@ -477,7 +477,7 @@
*
* <div class="alert alert-info">
* **Best Practice**: if you intend to add and remove transcluded content manually in your directive
* (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it),
* (by calling the transclude function to get the DOM and calling `element.remove()` to remove it),
* then you are also responsible for calling `$destroy` on the transclusion scope.
* </div>
*
@@ -2155,7 +2155,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
afterTemplateChildLinkFn,
beforeTemplateCompileNode = $compileNode[0],
origAsyncDirective = directives.shift(),
derivedSyncDirective = inherit(origAsyncDirective, {
// The fact that we have to copy and patch the directive seems wrong!
derivedSyncDirective = extend({}, origAsyncDirective, {
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
+1 -8
View File
@@ -1,7 +1,5 @@
'use strict';
var $controllerMinErr = minErr('$controller');
/**
* @ngdoc provider
* @name $controllerProvider
@@ -89,12 +87,7 @@ function $ControllerProvider() {
}
if (isString(expression)) {
match = expression.match(CNTRL_REG);
if (!match) {
throw $controllerMinErr('ctrlfmt',
"Badly formed controller string '{0}'. " +
"Must match `__name__ as __id__` or `__name__`.", expression);
}
match = expression.match(CNTRL_REG),
constructor = match[1],
identifier = identifier || match[3];
expression = controllers.hasOwnProperty(constructor)
+18 -6
View File
@@ -341,22 +341,34 @@
var ngAttributeAliasDirectives = {};
// boolean attrs are evaluated
forEach(BOOLEAN_ATTR, function(propName, attrName) {
// binding to multiple is not supported
if (propName == "multiple") return;
function defaultLinkFn(scope, element, attr) {
scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
attr.$set(attrName, !!value);
});
}
var normalized = directiveNormalize('ng-' + attrName);
var linkFn = defaultLinkFn;
if (propName === 'checked') {
linkFn = function(scope, element, attr) {
// ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
if (attr.ngModel !== attr[normalized]) {
defaultLinkFn(scope, element, attr);
}
};
}
ngAttributeAliasDirectives[normalized] = function() {
return {
restrict: 'A',
priority: 100,
link: function(scope, element, attr) {
scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
attr.$set(attrName, !!value);
});
}
link: linkFn
};
};
});
+4 -4
View File
@@ -492,19 +492,19 @@ var formDirectiveFactory = function(isNgForm) {
alias = controller.$name;
if (alias) {
setter(scope, null, alias, controller, alias);
setter(scope, alias, controller, alias);
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
if (alias === newValue) return;
setter(scope, null, alias, undefined, alias);
setter(scope, alias, undefined, alias);
alias = newValue;
setter(scope, null, alias, controller, alias);
setter(scope, alias, controller, alias);
parentFormCtrl.$$renameControl(controller, alias);
});
}
formElement.on('$destroy', function() {
parentFormCtrl.$removeControl(controller);
if (alias) {
setter(scope, null, alias, undefined, alias);
setter(scope, alias, undefined, alias);
}
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
});
+1 -1
View File
@@ -97,7 +97,7 @@ function classDirective(name, selector) {
function arrayClasses(classVal) {
if (isArray(classVal)) {
return classVal;
return classVal.join(' ').split(' ');
} else if (isString(classVal)) {
return classVal.split(' ');
} else if (isObject(classVal)) {
+6 -6
View File
@@ -285,10 +285,10 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
* committed value then `$render()` is called to update the input control.
* * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
* the `$viewValue` are different to last time.
* the `$viewValue` are different from last time.
*
* Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
* `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue`
* `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
* or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
* invoked if you only change a property on the objects.
*/
@@ -305,7 +305,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
*
* The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
*
* You can override this for input directives whose concept of being empty is different to the
* You can override this for input directives whose concept of being empty is different from the
* default. The `checkboxInputType` directive does this because in its case a value of `false`
* implies empty.
*
@@ -954,7 +954,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
* function that returns a representation of the model when called with zero arguments, and sets
* the internal state of a model when called with an argument. It's sometimes useful to use this
* for models that have an internal representation that's different than what the model exposes
* for models that have an internal representation that's different from what the model exposes
* to the view.
*
* <div class="alert alert-success">
@@ -1070,7 +1070,7 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
* takes place when a timer expires; this timer will be reset after another change takes place.
*
* Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
* be different than the value in the actual model. This means that if you update the model you
* be different from the value in the actual model. This means that if you update the model you
* should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
* order to make sure it is synchronized with the model and that any debounced action is canceled.
*
@@ -1215,7 +1215,7 @@ var ngModelOptionsDirective = function() {
restrict: 'A',
controller: ['$scope', '$attrs', function($scope, $attrs) {
var that = this;
this.$options = $scope.$eval($attrs.ngModelOptions);
this.$options = copy($scope.$eval($attrs.ngModelOptions));
// Allow adding/overriding bound events
if (this.$options.updateOn !== undefined) {
this.$options.updateOnDefault = false;
+612
View File
@@ -0,0 +1,612 @@
'use strict';
/* global jqLiteRemove */
var ngOptionsMinErr = minErr('ngOptions');
/**
* @ngdoc directive
* @name ngOptions
* @restrict A
*
* @description
*
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
* elements for the `<select>` element using the array or object obtained by evaluating the
* `ngOptions` comprehension expression.
*
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
* increasing speed by not creating a new scope for each repeated instance, as well as providing
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
* to a non-string value. This is because an option element can only be bound to string values at
* present.
*
* When an item in the `<select>` menu is selected, the array element or object property
* represented by the selected option will be bound to the model identified by the `ngModel`
* directive.
*
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
* option. See example below for demonstration.
*
* <div class="alert alert-warning">
* **Note:** `ngModel` compares by reference, not value. This is important when binding to an
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
* </div>
*
* ## `select` **`as`**
*
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
*
*
* ### `select` **`as`** and **`track by`**
*
* <div class="alert alert-warning">
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
* </div>
*
* Consider the following example:
*
* ```html
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
* ```
*
* ```js
* $scope.values = [{
* id: 1,
* label: 'aLabel',
* subItem: { name: 'aSubItem' }
* }, {
* id: 2,
* label: 'bLabel',
* subItem: { name: 'bSubItem' }
* }];
*
* $scope.selected = { name: 'aSubItem' };
* ```
*
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
* following:
*
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
* 2. Apply **`track by`** to the already selected value in `ngModel`.
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
* selected" option.
*
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} required The control is considered valid only if value is entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
* `required` when you want to data-bind to the `required` attribute.
* @param {comprehension_expression=} ngOptions in one of the following forms:
*
* * for array data sources:
* * `label` **`for`** `value` **`in`** `array`
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
* (for including a filter with `track by`)
* * for object data sources:
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`group by`** `group`
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
*
* Where:
*
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
* * `value`: local variable which will refer to each item in the `array` or each property value
* of `object` during iteration.
* * `key`: local variable which will refer to a property name in `object` during iteration.
* * `label`: The result of this expression will be the label for `<option>` element. The
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
* element. If not specified, `select` expression will default to `value`.
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
* DOM element.
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
* even when the options are recreated (e.g. reloaded from the server).
*
* @example
<example module="selectExample">
<file name="index.html">
<script>
angular.module('selectExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.colors = [
{name:'black', shade:'dark'},
{name:'white', shade:'light'},
{name:'red', shade:'dark'},
{name:'blue', shade:'dark'},
{name:'yellow', shade:'light'}
];
$scope.myColor = $scope.colors[2]; // red
}]);
</script>
<div ng-controller="ExampleController">
<ul>
<li ng-repeat="color in colors">
Name: <input ng-model="color.name">
[<a href ng-click="colors.splice($index, 1)">X</a>]
</li>
<li>
[<a href ng-click="colors.push({})">add</a>]
</li>
</ul>
<hr/>
Color (null not allowed):
<select ng-model="myColor" ng-options="color.name for color in colors"></select><br>
Color (null allowed):
<span class="nullable">
<select ng-model="myColor" ng-options="color.name for color in colors">
<option value="">-- choose color --</option>
</select>
</span><br/>
Color grouped by shade:
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
</select><br/>
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
<hr/>
Currently selected: {{ {selected_color:myColor} }}
<div style="border:solid 1px black; height:20px"
ng-style="{'background-color':myColor.name}">
</div>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should check ng-options', function() {
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
element.all(by.model('myColor')).first().click();
element.all(by.css('select[ng-model="myColor"] option')).first().click();
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
element(by.css('.nullable select[ng-model="myColor"]')).click();
element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
});
</file>
</example>
*/
// jshint maxlen: false
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
// 1: value expression (valueFn)
// 2: label expression (displayFn)
// 3: group by expression (groupByFn)
// 4: array item variable name
// 5: object item key variable name
// 6: object item value variable name
// 7: collection expression
// 8: track by expression
// jshint maxlen: 100
var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
function parseOptionsExpression(optionsExp, selectElement, scope) {
var match = optionsExp.match(NG_OPTIONS_REGEXP);
if (!(match)) {
throw ngOptionsMinErr('iexp',
"Expected expression in form of " +
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
" but got '{0}'. Element: {1}",
optionsExp, startingTag(selectElement));
}
// Extract the parts from the ngOptions expression
// The variable name for the value of the item in the collection
var valueName = match[4] || match[6];
// The variable name for the key of the item in the collection
var keyName = match[5];
// An expression that generates the viewValue for an option if there is a label expression
var selectAs = / as /.test(match[0]) && match[1];
// An expression that is used to track the id of each object in the options collection
var trackBy = match[8];
// An expression that generates the viewValue for an option if there is no label expression
var valueFn = $parse(match[2] ? match[1] : valueName);
var selectAsFn = selectAs && $parse(selectAs);
var viewValueFn = selectAsFn || valueFn;
var trackByFn = trackBy && $parse(trackBy);
// Get the value by which we are going to track the option
// if we have a trackFn then use that (passing scope and locals)
// otherwise just hash the given viewValue
var getTrackByValue = trackBy ?
function(viewValue, locals) { return trackByFn(scope, locals); } :
function getHashOfValue(viewValue) { return hashKey(viewValue); };
var displayFn = $parse(match[2] || match[1]);
var groupByFn = $parse(match[3] || '');
var valuesFn = $parse(match[7]);
var locals = {};
var getLocals = keyName ? function(value, key) {
locals[keyName] = key;
locals[valueName] = value;
return locals;
} : function(value) {
locals[valueName] = value;
return locals;
};
function Option(selectValue, viewValue, label, group) {
this.selectValue = selectValue;
this.viewValue = viewValue;
this.label = label;
this.group = group;
}
return {
getWatchables: $parse(valuesFn, function(values) {
// Create a collection of things that we would like to watch (watchedArray)
// so that they can all be watched using a single $watchCollection
// that only runs the handler once if anything changes
var watchedArray = [];
values = values || [];
Object.keys(values).forEach(function getWatchable(key) {
var locals = getLocals(values[key], key);
var label = displayFn(scope, locals);
var selectValue = getTrackByValue(values[key], locals);
watchedArray.push(selectValue);
watchedArray.push(label);
});
return watchedArray;
}),
getOptions: function() {
var optionItems = [];
var selectValueMap = {};
// The option values were already computed in the `getWatchables` fn,
// which must have been called to trigger `getOptions`
var optionValues = valuesFn(scope) || [];
var keys = Object.keys(optionValues);
keys.forEach(function getOption(key) {
// Ignore "angular" properties that start with $ or $$
if (key.charAt(0) === '$') return;
var value = optionValues[key];
var locals = getLocals(value, key);
var viewValue = viewValueFn(scope, locals);
var selectValue = getTrackByValue(viewValue, locals);
var label = displayFn(scope, locals);
var group = groupByFn(scope, locals);
var optionItem = new Option(selectValue, viewValue, label, group);
optionItems.push(optionItem);
selectValueMap[selectValue] = optionItem;
});
return {
items: optionItems,
selectValueMap: selectValueMap,
getOptionFromViewValue: function(value) {
return selectValueMap[getTrackByValue(value, getLocals(value))];
}
};
}
};
}
// we can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
var optionTemplate = document.createElement('option'),
optGroupTemplate = document.createElement('optgroup');
return {
restrict: 'A',
terminal: true,
require: ['select', '?ngModel'],
link: function(scope, selectElement, attr, ctrls) {
// if ngModel is not defined, we don't need to do anything
var ngModelCtrl = ctrls[1];
if (!ngModelCtrl) return;
var selectCtrl = ctrls[0];
var multiple = attr.multiple;
var emptyOption = selectCtrl.emptyOption;
var providedEmptyOption = !!emptyOption;
var unknownOption = jqLite(optionTemplate.cloneNode(false));
unknownOption.val('?');
var options;
var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope);
var renderEmptyOption = function() {
if (!providedEmptyOption) {
selectElement.prepend(emptyOption);
}
selectElement.val('');
emptyOption.prop('selected', true); // needed for IE
emptyOption.attr('selected', true);
};
var removeEmptyOption = function() {
if (!providedEmptyOption) {
emptyOption.remove();
}
};
var renderUnknownOption = function() {
selectElement.prepend(unknownOption);
selectElement.val('?');
unknownOption.prop('selected', true); // needed for IE
unknownOption.attr('selected', true);
};
var removeUnknownOption = function() {
unknownOption.remove();
};
selectCtrl.writeValue = function writeNgOptionsValue(value) {
var option = options.getOptionFromViewValue(value);
if (option) {
if (selectElement[0].value !== option.selectValue) {
removeUnknownOption();
removeEmptyOption();
selectElement[0].value = option.selectValue;
option.element.selected = true;
option.element.setAttribute('selected', 'selected');
}
} else {
if (value === null || providedEmptyOption) {
removeUnknownOption();
renderEmptyOption();
} else {
removeEmptyOption();
renderUnknownOption();
}
}
};
selectCtrl.readValue = function readNgOptionsValue() {
var selectedOption = options.selectValueMap[selectElement.val()];
if (selectedOption) {
removeEmptyOption();
removeUnknownOption();
return selectedOption.viewValue;
}
return null;
};
// Update the controller methods for multiple selectable options
if (multiple) {
ngModelCtrl.$isEmpty = function(value) {
return !value || value.length === 0;
};
selectCtrl.writeValue = function writeNgOptionsMultiple(value) {
options.items.forEach(function(option) {
option.element.selected = false;
});
if (value) {
value.forEach(function(item) {
var option = options.getOptionFromViewValue(item);
if (option) option.element.selected = true;
});
}
};
selectCtrl.readValue = function readNgOptionsMultiple() {
var selectedValues = selectElement.val() || [];
return selectedValues.map(function(selectedKey) {
var option = options.selectValueMap[selectedKey];
return option.viewValue;
});
};
}
if (providedEmptyOption) {
// we need to remove it before calling selectElement.empty() because otherwise IE will
// remove the label from the element. wtf?
emptyOption.remove();
// compile the element since there might be bindings in it
$compile(emptyOption)(scope);
// remove the class, which is added automatically because we recompile the element and it
// becomes the compilation root
emptyOption.removeClass('ng-scope');
} else {
emptyOption = jqLite(optionTemplate.cloneNode(false));
}
// We need to do this here to ensure that the options object is defined
// when we first hit it in writeNgOptionsValue
updateOptions();
// We will re-render the option elements if the option values or labels change
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
// ------------------------------------------------------------------ //
function updateOptionElement(option, element) {
option.element = element;
if (option.value !== element.value) element.value = option.selectValue;
if (option.label !== element.label) {
element.label = option.label;
element.textContent = option.label;
}
}
function addOrReuseElement(parent, current, type, templateElement) {
var element;
// Check whether we can reuse the next element
if (current && lowercase(current.nodeName) === type) {
// The next element is the right type so reuse it
element = current;
} else {
// The next element is not the right type so create a new one
element = templateElement.cloneNode(false);
if (!current) {
// There are no more elements so just append it to the select
parent.appendChild(element);
} else {
// The next element is not a group so insert the new one
parent.insertBefore(element, current);
}
}
return element;
}
function removeExcessElements(current) {
var next;
while (current) {
next = current.nextSibling;
jqLiteRemove(current);
current = next;
}
}
function skipEmptyAndUnknownOptions(current) {
var emptyOption_ = emptyOption && emptyOption[0];
var unknownOption_ = unknownOption && unknownOption[0];
if (emptyOption_ || unknownOption_) {
while (current &&
(current === emptyOption_ ||
current === unknownOption_)) {
current = current.nextSibling;
}
}
return current;
}
function updateOptions() {
var previousValue = options && selectCtrl.readValue();
options = ngOptions.getOptions();
var groupMap = {};
var currentElement = selectElement[0].firstChild;
// Ensure that the empty option is always there if it was explicitly provided
if (providedEmptyOption) {
selectElement.prepend(emptyOption);
}
currentElement = skipEmptyAndUnknownOptions(currentElement);
options.items.forEach(function updateOption(option) {
var group;
var groupElement;
var optionElement;
if (option.group) {
// This option is to live in a group
// See if we have already created this group
group = groupMap[option.group];
if (!group) {
// We have not already created this group
groupElement = addOrReuseElement(selectElement[0],
currentElement,
'optgroup',
optGroupTemplate);
// Move to the next element
currentElement = groupElement.nextSibling;
// Update the label on the group element
groupElement.label = option.group;
// Store it for use later
group = groupMap[option.group] = {
groupElement: groupElement,
currentOptionElement: groupElement.firstChild
};
}
// So now we have a group for this option we add the option to the group
optionElement = addOrReuseElement(group.groupElement,
group.currentOptionElement,
'option',
optionTemplate);
updateOptionElement(option, optionElement);
// Move to the next element
group.currentOptionElement = optionElement.nextSibling;
} else {
// This option is not in a group
optionElement = addOrReuseElement(selectElement[0],
currentElement,
'option',
optionTemplate);
updateOptionElement(option, optionElement);
// Move to the next element
currentElement = optionElement.nextSibling;
}
});
// Now remove all excess options and group
Object.keys(groupMap).forEach(function(key) {
removeExcessElements(groupMap[key].currentOptionElement);
});
removeExcessElements(currentElement);
ngModelCtrl.$render();
// Check to see if the value has changed due to the update to the options
if (!ngModelCtrl.$isEmpty(previousValue)) {
var nextValue = selectCtrl.readValue();
if (!equals(previousValue, nextValue)) {
ngModelCtrl.$setViewValue(nextValue);
}
}
}
}
};
}];
+15 -3
View File
@@ -54,6 +54,9 @@
* <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder
* for <span ng-non-bindable>{{numberExpression}}</span>.
*
* If no rule is defined for a category, then an empty string is displayed and a warning is generated.
* Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`.
*
* # Configuring ngPluralize with offset
* The `offset` attribute allows further customization of pluralized text, which can result in
* a better user experience. For example, instead of the message "4 people are viewing this document",
@@ -172,7 +175,7 @@
</file>
</example>
*/
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) {
var BRACE = /{}/g,
IS_WHEN = /^when(Minus)?(.+)$/;
@@ -214,9 +217,18 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
// If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
// In JS `NaN !== NaN`, so we have to exlicitly check.
if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) {
if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) {
watchRemover();
watchRemover = scope.$watch(whensExpFns[count], updateElementText);
var whenExpFn = whensExpFns[count];
if (isUndefined(whenExpFn)) {
if (newVal != null) {
$log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp);
}
watchRemover = noop;
updateElementText();
} else {
watchRemover = scope.$watch(whenExpFn, updateElementText);
}
lastCount = count;
}
});
+10 -10
View File
@@ -23,6 +23,7 @@
* 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
@@ -33,18 +34,19 @@
* ```
*
* 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**.
* it will return the keys for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive
* used to sort the keys alphabetically.)
*
* Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser
* when running `for key in myObj`. 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
*
* 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.
*
*
* # 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
@@ -358,14 +360,13 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
trackByIdFn = trackByIdExpFn || trackByIdArrayFn;
} else {
trackByIdFn = trackByIdExpFn || trackByIdObjFn;
// if object, extract keys, sort them and use to determine order of iteration over obj props
// if object, extract keys, in enumeration order, unsorted
collectionKeys = [];
for (var itemKey in collection) {
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') {
if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
collectionKeys.push(itemKey);
}
}
collectionKeys.sort();
}
collectionLength = collectionKeys.length;
@@ -461,4 +462,3 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
}
};
}];
+2 -3
View File
@@ -40,11 +40,10 @@ 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 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.
* class in CSS:
*
* ```css
* .ng-hide:not(.ng-hide-animate) {
* .ng-hide {
* /&#42; this is just another form of hiding an element &#42;/
* display: block!important;
* position: absolute;
+205 -695
View File
@@ -1,6 +1,111 @@
'use strict';
var ngOptionsMinErr = minErr('ngOptions');
var noopNgModelController = { $setViewValue: noop, $render: noop };
/**
* @ngdoc type
* @name select.SelectController
* @description
* The controller for the `<select>` directive. This provides support for reading
* and writing the selected value(s) of the control and also coordinates dynamically
* added `<option>` elements, perhaps by an `ngRepeat` directive.
*/
var SelectController =
['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var self = this,
optionsMap = new HashMap();
// If the ngModel doesn't get provided then provide a dummy noop version to prevent errors
self.ngModelCtrl = noopNgModelController;
// The "unknown" option is one that is prepended to the list if the viewValue
// does not match any of the options. When it is rendered the value of the unknown
// option is '? XXX ?' where XXX is the hashKey of the value that is not known.
//
// We can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
self.unknownOption = jqLite(document.createElement('option'));
self.renderUnknownOption = function(val) {
var unknownVal = '? ' + hashKey(val) + ' ?';
self.unknownOption.val(unknownVal);
$element.prepend(self.unknownOption);
$element.val(unknownVal);
};
$scope.$on('$destroy', function() {
// disable unknown option so that we don't do work when the whole select is being destroyed
self.renderUnknownOption = noop;
});
self.removeUnknownOption = function() {
if (self.unknownOption.parent()) self.unknownOption.remove();
};
// Here we find the option that represents the "empty" value, i.e. the option with a value
// of `""`. This option needs to be accessed (to select it directly) when setting the value
// of the select to `""` because IE9 will not automatically select the option.
//
// Additionally, the `ngOptions` directive uses this option to allow the application developer
// to provide their own custom "empty" option when the viewValue does not match any of the
// option values.
for (var i = 0, children = $element.children(), ii = children.length; i < ii; i++) {
if (children[i].value === '') {
self.emptyOption = children.eq(i);
break;
}
}
// Read the value of the select control, the implementation of this changes depending
// upon whether the select can have multiple values and whether ngOptions is at work.
self.readValue = function readSingleValue() {
self.removeUnknownOption();
return $element.val();
};
// Write the value to the select control, the implementation of this changes depending
// upon whether the select can have multiple values and whether ngOptions is at work.
self.writeValue = function writeSingleValue(value) {
if (self.hasOption(value)) {
self.removeUnknownOption();
$element.val(value);
if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy
} else {
if (isUndefined(value) && self.emptyOption) {
$element.val('');
} else {
self.renderUnknownOption(value);
}
}
};
// Tell the select control that an option, with the given value, has been added
self.addOption = function(value) {
assertNotHasOwnProperty(value, '"option value"');
var count = optionsMap.get(value) || 0;
optionsMap.put(value, count + 1);
};
// Tell the select control that an option, with the given value, has been removed
self.removeOption = function(value) {
var count = optionsMap.get(value);
if (count) {
if (count === 1) {
optionsMap.remove(value);
} else {
optionsMap.put(value, count - 1);
}
}
};
// Check whether the select control has an option matching the given value
self.hasOption = function(value) {
return !!optionsMap.get(value);
};
}];
/**
* @ngdoc directive
* @name select
@@ -9,24 +114,21 @@ var ngOptionsMinErr = minErr('ngOptions');
* @description
* HTML `SELECT` element with angular data-binding.
*
* # `ngOptions`
*
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
* elements for the `<select>` element using the array or object obtained by evaluating the
* `ngOptions` comprehension expression.
*
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
* similar result. However, `ngOptions` provides some benefits such as reducing memory and
* increasing speed by not creating a new scope for each repeated instance, as well as providing
* In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
* ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing
* memory and increasing speed by not creating a new scope for each repeated instance, as well as providing
* more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
* comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
* to a non-string value. This is because an option element can only be bound to string values at
* to a non-string value. This is because an option element can only be bound to string values at
* present.
*
* When an item in the `<select>` menu is selected, the array element or object property
* represented by the selected option will be bound to the model identified by the `ngModel`
* directive.
*
* If the viewValue contains a value that doesn't match any of the options then the control
* will automatically add an "unknown" option, which it then removes when this is resolved.
*
* Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
* be nested into the `<select>` element. This element will then represent the `null` or "not selected"
* option. See example below for demonstration.
@@ -36,307 +138,61 @@ var ngOptionsMinErr = minErr('ngOptions');
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
* </div>
*
* ## `select` **`as`**
*
* Using `select` **`as`** will bind the result of the `select` expression to the model, but
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
* or property name (for object data sources) of the value within the collection. If a **`track by`** expression
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
*
*
* ### `select` **`as`** and **`track by`**
*
* <div class="alert alert-warning">
* Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
* </div>
*
* Consider the following example:
*
* ```html
* <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
* ```
*
* ```js
* $scope.values = [{
* id: 1,
* label: 'aLabel',
* subItem: { name: 'aSubItem' }
* }, {
* id: 2,
* label: 'bLabel',
* subItem: { name: 'bSubItem' }
* }];
*
* $scope.selected = { name: 'aSubItem' };
* ```
*
* With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
* of the data source (to `item` in this example). To calculate whether an element is selected, we do the
* following:
*
* 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
* 2. Apply **`track by`** to the already selected value in `ngModel`.
* In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
* value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
* a wrong object, the selected element can't be found, `<select>` is always reset to the "not
* selected" option.
*
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} required The control is considered valid only if value is entered.
* @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
* the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
* `required` when you want to data-bind to the `required` attribute.
* @param {comprehension_expression=} ngOptions in one of the following forms:
*
* * for array data sources:
* * `label` **`for`** `value` **`in`** `array`
* * `select` **`as`** `label` **`for`** `value` **`in`** `array`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
* * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
* (for including a filter with `track by`)
* * for object data sources:
* * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
* * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`group by`** `group`
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
*
* Where:
*
* * `array` / `object`: an expression which evaluates to an array / object to iterate over.
* * `value`: local variable which will refer to each item in the `array` or each property value
* of `object` during iteration.
* * `key`: local variable which will refer to a property name in `object` during iteration.
* * `label`: The result of this expression will be the label for `<option>` element. The
* `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
* * `select`: The result of this expression will be bound to the model of the parent `<select>`
* element. If not specified, `select` expression will default to `value`.
* * `group`: The result of this expression will be used to group options using the `<optgroup>`
* DOM element.
* * `trackexpr`: Used when working with an array of objects. The result of this expression will be
* used to identify the objects in the array. The `trackexpr` will most likely refer to the
* `value` variable (e.g. `value.propertyName`). With this the selection is preserved
* even when the options are recreated (e.g. reloaded from the server).
*
* @example
<example module="selectExample">
<file name="index.html">
<script>
angular.module('selectExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.colors = [
{name:'black', shade:'dark'},
{name:'white', shade:'light'},
{name:'red', shade:'dark'},
{name:'blue', shade:'dark'},
{name:'yellow', shade:'light'}
];
$scope.myColor = $scope.colors[2]; // red
}]);
</script>
<div ng-controller="ExampleController">
<ul>
<li ng-repeat="color in colors">
Name: <input ng-model="color.name">
[<a href ng-click="colors.splice($index, 1)">X</a>]
</li>
<li>
[<a href ng-click="colors.push({})">add</a>]
</li>
</ul>
<hr/>
Color (null not allowed):
<select ng-model="myColor" ng-options="color.name for color in colors"></select><br>
Color (null allowed):
<span class="nullable">
<select ng-model="myColor" ng-options="color.name for color in colors">
<option value="">-- choose color --</option>
</select>
</span><br/>
Color grouped by shade:
<select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
</select><br/>
Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br>
<hr/>
Currently selected: {{ {selected_color:myColor} }}
<div style="border:solid 1px black; height:20px"
ng-style="{'background-color':myColor.name}">
</div>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should check ng-options', function() {
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
element.all(by.model('myColor')).first().click();
element.all(by.css('select[ng-model="myColor"] option')).first().click();
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
element(by.css('.nullable select[ng-model="myColor"]')).click();
element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
});
</file>
</example>
*/
var ngOptionsDirective = valueFn({
restrict: 'A',
terminal: true
});
// jshint maxlen: false
var selectDirective = ['$compile', '$parse', function($compile, $parse) {
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,
nullModelCtrl = {$setViewValue: noop};
// jshint maxlen: 100
var selectDirective = function() {
var lastView;
return {
restrict: 'E',
require: ['select', '?ngModel'],
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) {
var self = this,
optionsMap = {},
ngModelCtrl = nullModelCtrl,
nullOption,
unknownOption;
self.databound = $attrs.ngModel;
self.init = function(ngModelCtrl_, nullOption_, unknownOption_) {
ngModelCtrl = ngModelCtrl_;
nullOption = nullOption_;
unknownOption = unknownOption_;
};
self.addOption = function(value, element) {
assertNotHasOwnProperty(value, '"option value"');
optionsMap[value] = true;
if (ngModelCtrl.$viewValue == value) {
$element.val(value);
if (unknownOption.parent()) unknownOption.remove();
}
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
// Adding an <option selected="selected"> element to a <select required="required"> should
// automatically select the new element
if (element && element[0].hasAttribute('selected')) {
element[0].selected = true;
}
};
self.removeOption = function(value) {
if (this.hasOption(value)) {
delete optionsMap[value];
if (ngModelCtrl.$viewValue === value) {
this.renderUnknownOption(value);
}
}
};
self.renderUnknownOption = function(val) {
var unknownVal = '? ' + hashKey(val) + ' ?';
unknownOption.val(unknownVal);
$element.prepend(unknownOption);
$element.val(unknownVal);
unknownOption.prop('selected', true); // needed for IE
};
self.hasOption = function(value) {
return optionsMap.hasOwnProperty(value);
};
$scope.$on('$destroy', function() {
// disable unknown option so that we don't do work when the whole select is being destroyed
self.renderUnknownOption = noop;
});
}],
controller: SelectController,
link: function(scope, element, attr, ctrls) {
// if ngModel is not defined, we don't need to do anything
if (!ctrls[1]) return;
var ngModelCtrl = ctrls[1];
if (!ngModelCtrl) return;
var selectCtrl = ctrls[0],
ngModelCtrl = ctrls[1],
multiple = attr.multiple,
optionsExp = attr.ngOptions,
nullOption = false, // if false, user will not be able to select it (used by ngOptions)
emptyOption,
renderScheduled = false,
// we can't just jqLite('<option>') since jqLite is not smart enough
// to create it in <select> and IE barfs otherwise.
optionTemplate = jqLite(document.createElement('option')),
optGroupTemplate =jqLite(document.createElement('optgroup')),
unknownOption = optionTemplate.clone();
var selectCtrl = ctrls[0];
// find "null" option
for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) {
if (children[i].value === '') {
emptyOption = nullOption = children.eq(i);
break;
}
}
selectCtrl.ngModelCtrl = ngModelCtrl;
selectCtrl.init(ngModelCtrl, nullOption, unknownOption);
// We delegate rendering to the `writeValue` method, which can be changed
// if the select can have multiple selected values or if the options are being
// generated by `ngOptions`
ngModelCtrl.$render = function() {
selectCtrl.writeValue(ngModelCtrl.$viewValue);
};
// required validator
if (multiple) {
ngModelCtrl.$isEmpty = function(value) {
return !value || value.length === 0;
};
}
if (optionsExp) setupAsOptions(scope, element, ngModelCtrl);
else if (multiple) setupAsMultiple(scope, element, ngModelCtrl);
else setupAsSingle(scope, element, ngModelCtrl, selectCtrl);
////////////////////////////
function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) {
ngModelCtrl.$render = function() {
var viewValue = ngModelCtrl.$viewValue;
if (selectCtrl.hasOption(viewValue)) {
if (unknownOption.parent()) unknownOption.remove();
selectElement.val(viewValue);
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
} else {
if (isUndefined(viewValue) && emptyOption) {
selectElement.val('');
} else {
selectCtrl.renderUnknownOption(viewValue);
}
}
};
selectElement.on('change', function() {
scope.$apply(function() {
if (unknownOption.parent()) unknownOption.remove();
ngModelCtrl.$setViewValue(selectElement.val());
});
// When the selected item(s) changes we delegate getting the value of the select control
// to the `readValue` method, which can be changed if the select can have multiple
// selected values or if the options are being generated by `ngOptions`
element.on('change', function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(selectCtrl.readValue());
});
}
});
function setupAsMultiple(scope, selectElement, ctrl) {
var lastView;
ctrl.$render = function() {
var items = new HashMap(ctrl.$viewValue);
forEach(selectElement.find('option'), function(option) {
// If the select allows multiple values then we need to modify how we read and write
// values from and to the control; also what it means for the value to be empty and
// we have to add an extra watch since ngModel doesn't work well with arrays - it
// doesn't trigger rendering if only an item in the array changes.
if (attr.multiple) {
// Read value now needs to check each option to see if it is selected
selectCtrl.readValue = function readMultipleValue() {
var array = [];
forEach(element.find('option'), function(option) {
if (option.selected) {
array.push(option.value);
}
});
return array;
};
// Write value now needs to set the selected property of each matching option
selectCtrl.writeValue = function writeMultipleValue(value) {
var items = new HashMap(value);
forEach(element.find('option'), function(option) {
option.selected = isDefined(items.get(option.value));
});
};
@@ -344,400 +200,45 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// we have to do it on each watch since ngModel watches reference, but
// we need to work of an array, so we need to see if anything was inserted/removed
scope.$watch(function selectMultipleWatch() {
if (!equals(lastView, ctrl.$viewValue)) {
lastView = shallowCopy(ctrl.$viewValue);
ctrl.$render();
if (!equals(lastView, ngModelCtrl.$viewValue)) {
lastView = shallowCopy(ngModelCtrl.$viewValue);
ngModelCtrl.$render();
}
});
selectElement.on('change', function() {
scope.$apply(function() {
var array = [];
forEach(selectElement.find('option'), function(option) {
if (option.selected) {
array.push(option.value);
}
});
ctrl.$setViewValue(array);
});
});
}
// If we are a multiple select then value is now a collection
// so the meaning of $isEmpty changes
ngModelCtrl.$isEmpty = function(value) {
return !value || value.length === 0;
};
function setupAsOptions(scope, selectElement, ctrl) {
var match;
if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) {
throw ngOptionsMinErr('iexp',
"Expected expression in form of " +
"'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
" but got '{0}'. Element: {1}",
optionsExp, startingTag(selectElement));
}
var displayFn = $parse(match[2] || match[1]),
valueName = match[4] || match[6],
selectAs = / as /.test(match[0]) && match[1],
selectAsFn = selectAs ? $parse(selectAs) : null,
keyName = match[5],
groupByFn = $parse(match[3] || ''),
valueFn = $parse(match[2] ? match[1] : valueName),
valuesFn = $parse(match[7]),
track = match[8],
trackFn = track ? $parse(match[8]) : null,
trackKeysCache = {},
// This is an array of array of existing option groups in DOM.
// We try to reuse these if possible
// - optionGroupsCache[0] is the options with no option group
// - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element
optionGroupsCache = [[{element: selectElement, label:''}]],
//re-usable object to represent option's locals
locals = {};
if (nullOption) {
// compile the element since there might be bindings in it
$compile(nullOption)(scope);
// remove the class, which is added automatically because we recompile the element and it
// becomes the compilation root
nullOption.removeClass('ng-scope');
// we need to remove it before calling selectElement.empty() because otherwise IE will
// remove the label from the element. wtf?
nullOption.remove();
}
// clear contents, we'll add what's needed based on the model
selectElement.empty();
selectElement.on('change', selectionChanged);
ctrl.$render = render;
scope.$watchCollection(valuesFn, scheduleRendering);
scope.$watchCollection(getLabels, scheduleRendering);
if (multiple) {
scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering);
}
// ------------------------------------------------------------------ //
function callExpression(exprFn, key, value) {
locals[valueName] = value;
if (keyName) locals[keyName] = key;
return exprFn(scope, locals);
}
function selectionChanged() {
scope.$apply(function() {
var collection = valuesFn(scope) || [];
var viewValue;
if (multiple) {
viewValue = [];
forEach(selectElement.val(), function(selectedKey) {
selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey;
viewValue.push(getViewValue(selectedKey, collection[selectedKey]));
});
} else {
var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val();
viewValue = getViewValue(selectedKey, collection[selectedKey]);
}
ctrl.$setViewValue(viewValue);
render();
});
}
function getViewValue(key, value) {
if (key === '?') {
return undefined;
} else if (key === '') {
return null;
} else {
var viewValueFn = selectAsFn ? selectAsFn : valueFn;
return callExpression(viewValueFn, key, value);
}
}
function getLabels() {
var values = valuesFn(scope);
var toDisplay;
if (values && isArray(values)) {
toDisplay = new Array(values.length);
for (var i = 0, ii = values.length; i < ii; i++) {
toDisplay[i] = callExpression(displayFn, i, values[i]);
}
return toDisplay;
} else if (values) {
// TODO: Add a test for this case
toDisplay = {};
for (var prop in values) {
if (values.hasOwnProperty(prop)) {
toDisplay[prop] = callExpression(displayFn, prop, values[prop]);
}
}
}
return toDisplay;
}
function createIsSelectedFn(viewValue) {
var selectedSet;
if (multiple) {
if (trackFn && isArray(viewValue)) {
selectedSet = new HashMap([]);
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
// tracking by key
selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true);
}
} else {
selectedSet = new HashMap(viewValue);
}
} else if (trackFn) {
viewValue = callExpression(trackFn, null, viewValue);
}
return function isSelected(key, value) {
var compareValueFn;
if (trackFn) {
compareValueFn = trackFn;
} else if (selectAsFn) {
compareValueFn = selectAsFn;
} else {
compareValueFn = valueFn;
}
if (multiple) {
return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value)));
} else {
return viewValue === callExpression(compareValueFn, key, value);
}
};
}
function scheduleRendering() {
if (!renderScheduled) {
scope.$$postDigest(render);
renderScheduled = true;
}
}
/**
* A new labelMap is created with each render.
* This function is called for each existing option with added=false,
* and each new option with added=true.
* - Labels that are passed to this method twice,
* (once with added=true and once with added=false) will end up with a value of 0, and
* will cause no change to happen to the corresponding option.
* - Labels that are passed to this method only once with added=false will end up with a
* value of -1 and will eventually be passed to selectCtrl.removeOption()
* - Labels that are passed to this method only once with added=true will end up with a
* value of 1 and will eventually be passed to selectCtrl.addOption()
*/
function updateLabelMap(labelMap, label, added) {
labelMap[label] = labelMap[label] || 0;
labelMap[label] += (added ? 1 : -1);
}
function render() {
renderScheduled = false;
// Temporary location for the option groups before we render them
var optionGroups = {'':[]},
optionGroupNames = [''],
optionGroupName,
optionGroup,
option,
existingParent, existingOptions, existingOption,
viewValue = ctrl.$viewValue,
values = valuesFn(scope) || [],
keys = keyName ? sortedKeys(values) : values,
key,
value,
groupLength, length,
groupIndex, index,
labelMap = {},
selected,
isSelected = createIsSelectedFn(viewValue),
anySelected = false,
lastElement,
element,
label,
optionId;
trackKeysCache = {};
// We now build up the list of options we need (we merge later)
for (index = 0; length = keys.length, index < length; index++) {
key = index;
if (keyName) {
key = keys[index];
if (key.charAt(0) === '$') continue;
}
value = values[key];
optionGroupName = callExpression(groupByFn, key, value) || '';
if (!(optionGroup = optionGroups[optionGroupName])) {
optionGroup = optionGroups[optionGroupName] = [];
optionGroupNames.push(optionGroupName);
}
selected = isSelected(key, value);
anySelected = anySelected || selected;
label = callExpression(displayFn, key, value); // what will be seen by the user
// doing displayFn(scope, locals) || '' overwrites zero values
label = isDefined(label) ? label : '';
optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index);
if (trackFn) {
trackKeysCache[optionId] = key;
}
optionGroup.push({
// either the index into array or key from object
id: optionId,
label: label,
selected: selected // determine if we should be selected
});
}
if (!multiple) {
if (nullOption || viewValue === null) {
// insert null option if we have a placeholder, or the model is null
optionGroups[''].unshift({id:'', label:'', selected:!anySelected});
} else if (!anySelected) {
// option could not be found, we have to insert the undefined item
optionGroups[''].unshift({id:'?', label:'', selected:true});
}
}
// Now we need to update the list of DOM nodes to match the optionGroups we computed above
for (groupIndex = 0, groupLength = optionGroupNames.length;
groupIndex < groupLength;
groupIndex++) {
// current option group name or '' if no group
optionGroupName = optionGroupNames[groupIndex];
// list of options for that group. (first item has the parent)
optionGroup = optionGroups[optionGroupName];
if (optionGroupsCache.length <= groupIndex) {
// we need to grow the optionGroups
existingParent = {
element: optGroupTemplate.clone().attr('label', optionGroupName),
label: optionGroup.label
};
existingOptions = [existingParent];
optionGroupsCache.push(existingOptions);
selectElement.append(existingParent.element);
} else {
existingOptions = optionGroupsCache[groupIndex];
existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element
// update the OPTGROUP label if not the same.
if (existingParent.label != optionGroupName) {
existingParent.element.attr('label', existingParent.label = optionGroupName);
}
}
lastElement = null; // start at the beginning
for (index = 0, length = optionGroup.length; index < length; index++) {
option = optionGroup[index];
if ((existingOption = existingOptions[index + 1])) {
// reuse elements
lastElement = existingOption.element;
if (existingOption.label !== option.label) {
updateLabelMap(labelMap, existingOption.label, false);
updateLabelMap(labelMap, option.label, true);
lastElement.text(existingOption.label = option.label);
lastElement.prop('label', existingOption.label);
}
if (existingOption.id !== option.id) {
lastElement.val(existingOption.id = option.id);
}
// lastElement.prop('selected') provided by jQuery has side-effects
if (lastElement[0].selected !== option.selected) {
lastElement.prop('selected', (existingOption.selected = option.selected));
if (msie) {
// See #7692
// The selected item wouldn't visually update on IE without this.
// Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well
lastElement.prop('selected', existingOption.selected);
}
}
} else {
// grow elements
// if it's a null option
if (option.id === '' && nullOption) {
// put back the pre-compiled element
element = nullOption;
} else {
// jQuery(v1.4.2) Bug: We should be able to chain the method calls, but
// in this version of jQuery on some browser the .text() returns a string
// rather then the element.
(element = optionTemplate.clone())
.val(option.id)
.prop('selected', option.selected)
.attr('selected', option.selected)
.prop('label', option.label)
.text(option.label);
}
existingOptions.push(existingOption = {
element: element,
label: option.label,
id: option.id,
selected: option.selected
});
updateLabelMap(labelMap, option.label, true);
if (lastElement) {
lastElement.after(element);
} else {
existingParent.element.append(element);
}
lastElement = element;
}
}
// remove any excessive OPTIONs in a group
index++; // increment since the existingOptions[0] is parent element not OPTION
while (existingOptions.length > index) {
option = existingOptions.pop();
updateLabelMap(labelMap, option.label, false);
option.element.remove();
}
}
// remove any excessive OPTGROUPs from select
while (optionGroupsCache.length > groupIndex) {
// remove all the labels in the option group
optionGroup = optionGroupsCache.pop();
for (index = 1; index < optionGroup.length; ++index) {
updateLabelMap(labelMap, optionGroup[index].label, false);
}
optionGroup[0].element.remove();
}
forEach(labelMap, function(count, label) {
if (count > 0) {
selectCtrl.addOption(label);
} else if (count < 0) {
selectCtrl.removeOption(label);
}
});
}
}
}
};
}];
};
// The option directive is purely designed to communicate the existence (or lack of)
// of dynamically created (and destroyed) option elements to their containing select
// directive via its controller.
var optionDirective = ['$interpolate', function($interpolate) {
var nullSelectCtrl = {
addOption: noop,
removeOption: noop
};
function chromeHack(optionElement) {
// Workaround for https://code.google.com/p/chromium/issues/detail?id=381459
// Adding an <option selected="selected"> element to a <select required="required"> should
// automatically select the new element
if (optionElement[0].hasAttribute('selected')) {
optionElement[0].selected = true;
}
}
return {
restrict: 'E',
priority: 100,
compile: function(element, attr) {
// If the value attribute is not defined then we fall back to the
// text content of the option element, which may be interpolated
if (isUndefined(attr.value)) {
var interpolateFn = $interpolate(element.text(), true);
if (!interpolateFn) {
@@ -746,30 +247,39 @@ var optionDirective = ['$interpolate', function($interpolate) {
}
return function(scope, element, attr) {
// This is an optimization over using ^^ since we don't want to have to search
// all the way to the root of the DOM for every single option element
var selectCtrlName = '$selectController',
parent = element.parent(),
selectCtrl = parent.data(selectCtrlName) ||
parent.parent().data(selectCtrlName); // in case we are in optgroup
if (!selectCtrl || !selectCtrl.databound) {
selectCtrl = nullSelectCtrl;
}
// Only update trigger option updates if this is an option within a `select`
// that also has `ngModel` attached
if (selectCtrl && selectCtrl.ngModelCtrl) {
if (interpolateFn) {
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
attr.$set('value', newVal);
if (oldVal !== newVal) {
selectCtrl.removeOption(oldVal);
}
selectCtrl.addOption(newVal, element);
if (interpolateFn) {
scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) {
attr.$set('value', newVal);
if (oldVal !== newVal) {
selectCtrl.removeOption(oldVal);
}
selectCtrl.addOption(newVal, element);
selectCtrl.ngModelCtrl.$render();
chromeHack(element);
});
} else {
selectCtrl.addOption(attr.value, element);
selectCtrl.ngModelCtrl.$render();
chromeHack(element);
}
element.on('$destroy', function() {
selectCtrl.removeOption(attr.value);
selectCtrl.ngModelCtrl.$render();
});
} else {
selectCtrl.addOption(attr.value, element);
}
element.on('$destroy', function() {
selectCtrl.removeOption(attr.value);
});
};
}
};
+3 -3
View File
@@ -60,12 +60,12 @@ var maxlengthDirective = function() {
var maxlength = -1;
attr.$observe('maxlength', function(value) {
var intVal = int(value);
var intVal = toInt(value);
maxlength = isNaN(intVal) ? -1 : intVal;
ctrl.$validate();
});
ctrl.$validators.maxlength = function(modelValue, viewValue) {
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
};
}
};
@@ -80,7 +80,7 @@ var minlengthDirective = function() {
var minlength = 0;
attr.$observe('minlength', function(value) {
minlength = int(value) || 0;
minlength = toInt(value) || 0;
ctrl.$validate();
});
ctrl.$validators.minlength = function(modelValue, viewValue) {
+7 -1
View File
@@ -124,7 +124,13 @@
*/
function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;
if (!isArray(array)) {
if (array == null) {
return array;
} else {
throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
}
}
var predicateFn;
var matchAgainstAnyProp;
+20 -12
View File
@@ -82,6 +82,8 @@ function currencyFilter($locale) {
*
* If the input is not a number an empty string is returned.
*
* If the input is an infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
*
* @param {number|string} number Number to format.
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
* If this is not provided then the fraction size is computed from the current locale's number
@@ -138,16 +140,22 @@ function numberFilter($locale) {
var DECIMAL_SEP = '.';
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
if (!isFinite(number) || isObject(number)) return '';
if (isObject(number)) return '';
var isNegative = number < 0;
number = Math.abs(number);
var isInfinity = number === Infinity;
if (!isInfinity && !isFinite(number)) return '';
var numStr = number + '',
formatedText = '',
hasExponent = false,
parts = [];
var hasExponent = false;
if (numStr.indexOf('e') !== -1) {
if (isInfinity) formatedText = '\u221e';
if (!isInfinity && numStr.indexOf('e') !== -1) {
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
number = 0;
@@ -157,7 +165,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
}
}
if (!hasExponent) {
if (!isInfinity && !hasExponent) {
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
// determine fractionSize if it is not specified
@@ -429,13 +437,13 @@ function dateFilter($locale) {
timeSetter = match[8] ? date.setUTCHours : date.setHours;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
tzHour = toInt(match[9] + match[10]);
tzMin = toInt(match[9] + match[11]);
}
dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
var h = int(match[4] || 0) - tzHour;
var m = int(match[5] || 0) - tzMin;
var s = int(match[6] || 0);
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
var h = toInt(match[4] || 0) - tzHour;
var m = toInt(match[5] || 0) - tzMin;
var s = toInt(match[6] || 0);
var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
timeSetter.call(date, h, m, s, ms);
return date;
@@ -452,14 +460,14 @@ function dateFilter($locale) {
format = format || 'mediumDate';
format = $locale.DATETIME_FORMATS[format] || format;
if (isString(date)) {
date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date);
date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
}
if (isNumber(date)) {
date = new Date(date);
}
if (!isDate(date)) {
if (!isDate(date) || !isFinite(date.getTime())) {
return date;
}
+8 -11
View File
@@ -15,7 +15,8 @@
* @param {string|number} limit The length of the returned array or string. If the `limit` number
* is positive, `limit` number of items from the beginning of the source array/string are copied.
* If the number is negative, `limit` number of items from the end of the source array/string
* are copied. The `limit` will be trimmed if it exceeds `array.length`
* are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
* the input will be returned unchanged.
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
* had less than `limit` elements.
*
@@ -88,20 +89,16 @@
*/
function limitToFilter() {
return function(input, limit) {
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
if (Math.abs(Number(limit)) === Infinity) {
limit = Number(limit);
} else {
limit = int(limit);
limit = toInt(limit);
}
if (isNaN(limit)) return input;
//NaN check on limit
if (limit) {
return limit > 0 ? input.slice(0, limit) : input.slice(limit);
} else {
return isString(input) ? "" : [];
}
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
return limit >= 0 ? input.slice(0, limit) : input.slice(limit);
};
}
+5 -5
View File
@@ -347,7 +347,7 @@ function $HttpProvider() {
* To add or overwrite these defaults, simply add or remove a property from these configuration
* objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
* with the lowercased HTTP method name as the key, e.g.
* `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
* `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`.
*
* The defaults can also be set at runtime via the `$http.defaults` object in the same
* fashion. For example:
@@ -615,7 +615,7 @@ function $HttpProvider() {
* - **data** `{string|Object}` Data to be sent as the request message data.
* - **headers** `{Object}` Map of strings or functions which return strings representing
* HTTP headers to send to the server. If the return value of a function is null, the
* header will not be sent.
* header will not be sent. Functions accept a config object as an argument.
* - **xsrfHeaderName** `{string}` Name of HTTP header to populate with the XSRF token.
* - **xsrfCookieName** `{string}` Name of cookie containing the XSRF token.
* - **transformRequest**
@@ -834,12 +834,12 @@ function $HttpProvider() {
: $q.reject(resp);
}
function executeHeaderFns(headers) {
function executeHeaderFns(headers, config) {
var headerContent, processedHeaders = {};
forEach(headers, function(headerFn, header) {
if (isFunction(headerFn)) {
headerContent = headerFn();
headerContent = headerFn(config);
if (headerContent != null) {
processedHeaders[header] = headerContent;
}
@@ -873,7 +873,7 @@ function $HttpProvider() {
}
// execute if header value is a function for merged headers
return executeHeaderFns(reqHeaders);
return executeHeaderFns(reqHeaders, shallowCopy(config));
}
}
+2 -2
View File
@@ -27,7 +27,7 @@ function parseAbsoluteUrl(absoluteUrl, locationObj) {
locationObj.$$protocol = parsedUrl.protocol;
locationObj.$$host = parsedUrl.hostname;
locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null;
}
@@ -879,7 +879,7 @@ function $LocationProvider() {
// rewrite hashbang url <> html5 url
if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
if ($location.absUrl() != initialUrl) {
$browser.url($location.absUrl(), true);
}
+7 -13
View File
@@ -664,7 +664,7 @@ Parser.prototype = {
}, {
assign: function(scope, value, locals) {
var o = object(scope, locals);
if (!o) object.assign(scope, o = {}, locals);
if (!o) object.assign(scope, o = {});
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 = {}, locals);
if (!o) obj.assign(self, o = {});
return o[key] = value;
}
});
@@ -728,11 +728,6 @@ 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);
};
},
@@ -805,19 +800,18 @@ Parser.prototype = {
// Parser helper functions
//////////////////////////////////////////////////
function setter(obj, locals, path, setValue, fullExp) {
function setter(obj, 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 = (i === 0 && locals && locals[key]) || obj[key];
var propertyObj = ensureSafeObject(obj[key], fullExp);
if (!propertyObj) {
propertyObj = {};
obj[key] = propertyObj;
}
obj = ensureSafeObject(propertyObj, fullExp);
obj = propertyObj;
}
key = ensureSafeMemberName(element.shift(), fullExp);
ensureSafeObject(obj[key], fullExp);
@@ -944,8 +938,8 @@ function getterFn(path, options, fullExp) {
}
fn.sharedGetter = true;
fn.assign = function(self, value, locals) {
return setter(self, locals, path, value, path);
fn.assign = function(self, value) {
return setter(self, path, value, path);
};
getterFnCache[path] = fn;
return fn;
+8 -7
View File
@@ -302,24 +302,24 @@ function qFactory(nextTick, exceptionHandler) {
}
function processQueue(state) {
var fn, promise, pending;
var fn, deferred, pending;
pending = state.pending;
state.processScheduled = false;
state.pending = undefined;
for (var i = 0, ii = pending.length; i < ii; ++i) {
promise = pending[i][0];
deferred = pending[i][0];
fn = pending[i][state.status];
try {
if (isFunction(fn)) {
promise.resolve(fn(state.value));
deferred.resolve(fn(state.value));
} else if (state.status === 1) {
promise.resolve(state.value);
deferred.resolve(state.value);
} else {
promise.reject(state.value);
deferred.reject(state.value);
}
} catch (e) {
promise.reject(e);
deferred.reject(e);
exceptionHandler(e);
}
}
@@ -347,7 +347,8 @@ function qFactory(nextTick, exceptionHandler) {
'qcycle',
"Expected promise to be resolved with value other than itself '{0}'",
val));
} else {
}
else {
this.$$resolve(val);
}
+3 -3
View File
@@ -18,7 +18,7 @@ function $SnifferProvider() {
this.$get = ['$window', '$document', function($window, $document) {
var eventSupport = {},
android =
int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
document = $document[0] || {},
vendorPrefix,
@@ -45,8 +45,8 @@ function $SnifferProvider() {
animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
if (android && (!transitions || !animations)) {
transitions = isString(document.body.style.webkitTransition);
animations = isString(document.body.style.webkitAnimation);
transitions = isString(bodyStyle.webkitTransition);
animations = isString(bodyStyle.webkitAnimation);
}
}
+4 -2
View File
@@ -15,7 +15,7 @@ var $compileMinErr = minErr('$compile');
* @param {string} tpl The HTTP request template URL
* @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
*
* @return {Promise} the HTTP Promise for the given.
* @return {Promise} a promise for the HTTP response data of the given URL.
*
* @property {number} totalPendingRequests total amount of pending template requests being downloaded.
*/
@@ -44,12 +44,14 @@ function $TemplateRequestProvider() {
handleRequestFn.totalPendingRequests--;
})
.then(function(response) {
$templateCache.put(tpl, response.data);
return response.data;
}, handleError);
function handleError(resp) {
if (!ignoreRequestError) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
tpl, resp.status, resp.statusText);
}
return $q.reject(resp);
}
+13 -3
View File
@@ -4,6 +4,7 @@
function $TimeoutProvider() {
this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
function($rootScope, $browser, $q, $$q, $exceptionHandler) {
var deferreds = {};
@@ -16,15 +17,18 @@ function $TimeoutProvider() {
* block and delegates any exceptions to
* {@link ng.$exceptionHandler $exceptionHandler} service.
*
* The return value of registering a timeout function is a promise, which will be resolved when
* the timeout is reached and the timeout function is executed.
* The return value of calling `$timeout` is a promise, which will be resolved when
* the delay has passed and the timeout function, if provided, is executed.
*
* To cancel a timeout request, call `$timeout.cancel(promise)`.
*
* In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
* synchronously flush the queue of deferred functions.
*
* @param {function()} fn A function, whose execution should be delayed.
* If you only want a promise that will be resolved after some specified delay
* then you can call `$timeout` without the `fn` function.
*
* @param {function()=} fn A function, whose execution should be delayed.
* @param {number=} [delay=0] Delay in milliseconds.
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
@@ -33,6 +37,12 @@ function $TimeoutProvider() {
*
*/
function timeout(fn, delay, invokeApply) {
if (!isFunction(fn)) {
invokeApply = delay;
delay = fn;
fn = noop;
}
var skipApply = (isDefined(invokeApply) && !invokeApply),
deferred = (skipApply ? $$q : $q).defer(),
promise = deferred.promise,
+2 -1
View File
@@ -1327,7 +1327,8 @@ 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;
+2 -10
View File
@@ -100,8 +100,7 @@ function $AriaProvider() {
* - **ariaMultiline** `{boolean}` Enables/disables aria-multiline tags
* - **ariaValue** `{boolean}` Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
* - **tabindex** `{boolean}` Enables/disables tabindex tags
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on `&lt;div&gt;` and
* `&lt;li&gt;` elements with ng-click
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on ng-click
*
* @description
* Enables/disables various ARIA attributes
@@ -304,18 +303,11 @@ 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 && isNodeOneOf(elem, ['DIV', 'LI'])) {
if ($aria.config('bindKeypress') && !attr.ngKeypress) {
elem.on('keypress', function(event) {
if (event.keyCode === 32 || event.keyCode === 13) {
scope.$apply(callback);
+1 -1
View File
@@ -160,7 +160,7 @@ angular.module('ngCookies', ['ng']).
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value.
* @returns {Object} Deserialized cookie value, undefined if the cookie does not exist.
*/
get: function(key) {
var value = $cookies[key];
+1 -1
View File
@@ -81,7 +81,7 @@ $provide.value("$locale", {
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "\u20ac",
"CURRENCY_SYM": "Lt",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
+1 -1
View File
@@ -81,7 +81,7 @@ $provide.value("$locale", {
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "\u20ac",
"CURRENCY_SYM": "Lt",
"DECIMAL_SEP": ",",
"GROUP_SEP": "\u00a0",
"PATTERNS": [
+14 -14
View File
@@ -40,26 +40,26 @@ $provide.value("$locale", {
"\u1005\u1014\u1031"
],
"SHORTMONTH": [
"\u1007\u1014\u103a",
"\u1016\u1031",
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
"\u1019\u1010\u103a",
"\u1027\u1015\u103c\u102e",
"\u1019\u1031",
"\u1007\u103d\u1014\u103a",
"\u1007\u1030",
"\u1029",
"\u1005\u1000\u103a",
"\u1021\u1031\u102c\u1000\u103a",
"\u1014\u102d\u102f",
"\u1012\u102e"
"\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"
],
"fullDate": "EEEE, dd MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"fullDate": "EEEE, y MMMM dd",
"longDate": "y MMMM d",
"medium": "y MMM d HH:mm:ss",
"mediumDate": "y MMM d",
"mediumTime": "HH:mm:ss",
"short": "dd-MM-yy HH:mm",
"shortDate": "dd-MM-yy",
"short": "yy/MM/dd HH:mm",
"shortDate": "yy/MM/dd",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
+14 -14
View File
@@ -40,26 +40,26 @@ $provide.value("$locale", {
"\u1005\u1014\u1031"
],
"SHORTMONTH": [
"\u1007\u1014\u103a",
"\u1016\u1031",
"\u1007\u1014\u103a\u1014\u101d\u102b\u101b\u102e",
"\u1016\u1031\u1016\u1031\u102c\u103a\u101d\u102b\u101b\u102e",
"\u1019\u1010\u103a",
"\u1027\u1015\u103c\u102e",
"\u1019\u1031",
"\u1007\u103d\u1014\u103a",
"\u1007\u1030",
"\u1029",
"\u1005\u1000\u103a",
"\u1021\u1031\u102c\u1000\u103a",
"\u1014\u102d\u102f",
"\u1012\u102e"
"\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"
],
"fullDate": "EEEE, dd MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"fullDate": "EEEE, y MMMM dd",
"longDate": "y MMMM d",
"medium": "y MMM d HH:mm:ss",
"mediumDate": "y MMM d",
"mediumTime": "HH:mm:ss",
"short": "dd-MM-yy HH:mm",
"shortDate": "dd-MM-yy",
"short": "yy/MM/dd HH:mm",
"shortDate": "yy/MM/dd",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
+3 -3
View File
@@ -70,7 +70,7 @@
*
* However, including generic messages may not be useful enough to match all input fields, therefore,
* `ngMessages` provides the ability to override messages defined in the remote template by redefining
* then within the directive container.
* them within the directive container.
*
* ```html
* <!-- a generic template of error messages known as "my-custom-messages" -->
@@ -109,7 +109,7 @@
*
* Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS
* class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no
* animations present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
* messages present. Therefore, CSS transitions and keyframes as well as JavaScript animations can
* hook into the animations whenever these classes are added/removed.
*
* Let's say that our HTML code for our messages container looks like so:
@@ -380,7 +380,7 @@ angular.module('ngMessages', [])
});
}
},
detach: function(now) {
detach: function() {
if (element) {
$animate.leave(element);
element = null;
+54 -68
View File
@@ -574,20 +574,20 @@ function jsonStringToDate(string) {
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
tzHour = toInt(match[9] + match[10]);
tzMin = toInt(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4] || 0) - tzHour,
int(match[5] || 0) - tzMin,
int(match[6] || 0),
int(match[7] || 0));
date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
date.setUTCHours(toInt(match[4] || 0) - tzHour,
toInt(match[5] || 0) - tzMin,
toInt(match[6] || 0),
toInt(match[7] || 0));
return date;
}
return string;
}
function int(str) {
function toInt(str) {
return parseInt(str, 10);
}
@@ -1227,8 +1227,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* Creates a new backend definition.
*
* @param {string} method HTTP method.
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
@@ -1273,8 +1273,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for GET requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1287,8 +1287,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for HEAD requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1301,8 +1301,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for DELETE requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1315,8 +1315,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for POST requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
@@ -1331,8 +1331,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for PUT requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
@@ -1347,8 +1347,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new backend definition for JSONP requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
@@ -1363,8 +1363,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* Creates a new request expectation.
*
* @param {string} method HTTP method.
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1402,8 +1402,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for GET requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1416,8 +1416,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for HEAD requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1430,8 +1430,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for DELETE requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {Object=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
@@ -1444,8 +1444,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for POST requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1461,8 +1461,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for PUT requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1478,8 +1478,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for PATCH requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
@@ -1495,8 +1495,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @description
* Creates a new request expectation for JSONP requests. For more info see `expect()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives an url
* and returns true if the url matches the current definition.
* @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
@@ -1906,8 +1906,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* Creates a new backend definition.
*
* @param {string} method HTTP method.
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current definition.
@@ -1934,8 +1934,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for GET requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
@@ -1949,8 +1949,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for HEAD requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
@@ -1964,8 +1964,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for DELETE requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
@@ -1979,8 +1979,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for POST requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
@@ -1995,8 +1995,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for PUT requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
@@ -2011,8 +2011,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for PATCH requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @param {(string|RegExp)=} data HTTP request body.
* @param {(Object|function(Object))=} headers HTTP headers.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
@@ -2027,8 +2027,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
* @description
* Creates a new backend definition for JSONP requests. For more info see `when()`.
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {string|RegExp|function(string)} url HTTP url or function that receives a url
* and returns true if the url matches the current definition.
* @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
* control how a matched request is handled. You can save this object for later use and invoke
* `respond` or `passThrough` again in order to change how a matched request is handled.
@@ -2127,32 +2127,18 @@ 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;
+11 -5
View File
@@ -477,15 +477,21 @@ function $RouteProvider() {
* definitions will be interpolated into the location's path, while
* remaining properties will be treated as query params.
*
* @param {!Object<string, string>} newParams mapping of URL parameter names to values
* @param {Object} 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));
// interpolate modifies newParams, only query params are left
$location.search(newParams);
} else {
$location.search(angular.extend({}, $location.search(), searchParams));
}
else {
throw $routeMinErr('norout', 'Tried updating route when with no current route');
}
}
@@ -601,8 +607,8 @@ function $RouteProvider() {
return $q.all(locals);
}
}).
// after route change
then(function(locals) {
// after route change
if (nextRoute == $route.current) {
if (nextRoute) {
nextRoute.locals = locals;
+9 -10
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,8 +336,7 @@ function htmlParser(html, handler) {
}
} else {
// 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'),
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
function(all, text) {
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
@@ -361,17 +360,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);
@@ -396,13 +395,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;
+10 -29
View File
@@ -68,31 +68,19 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
try {
var $window = self.getWindow_();
if (!$window.angular) {
self.executeAction(loadFn);
return;
}
if (!$window.angular.resumeBootstrap) {
$window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
} else {
resumeDeferredBootstrap();
if ($window.angular) {
// Disable animations
$window.angular.resumeBootstrap([['$provide', function($provide) {
return ['$animate', function($animate) {
$animate.enabled(false);
}];
}]]);
}
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
@@ -117,14 +105,7 @@ angular.scenario.Application.prototype.executeAction = function(action) {
if (!$window.angular) {
return action.call(this, $window, _jQuery($window.document));
}
if (!!this.rootElement) {
executeWithElement(this.rootElement);
} else {
angularInit($window.document, angular.bind(this, executeWithElement));
}
function executeWithElement(element) {
angularInit($window.document, function(element) {
var $injector = $window.angular.element(element).injector();
var $element = _jQuery(element);
@@ -137,5 +118,5 @@ angular.scenario.Application.prototype.executeAction = function(action) {
action.call(self, $window, $element);
});
});
}
});
};
+3 -3
View File
@@ -114,7 +114,7 @@ angular.scenario.ObjectModel = function(runner) {
});
function complete(item) {
item.endTime = new Date().getTime();
item.endTime = Date.now();
item.duration = item.endTime - item.startTime;
item.status = item.status || 'success';
}
@@ -188,7 +188,7 @@ angular.scenario.ObjectModel.prototype.getSpec = function(id) {
angular.scenario.ObjectModel.Spec = function(id, name, definitionNames) {
this.id = id;
this.name = name;
this.startTime = new Date().getTime();
this.startTime = Date.now();
this.steps = [];
this.fullDefinitionName = (definitionNames || []).join(' ');
};
@@ -234,7 +234,7 @@ angular.scenario.ObjectModel.Spec.prototype.setStatusFromStep = function(step) {
*/
angular.scenario.ObjectModel.Step = function(name) {
this.name = name;
this.startTime = new Date().getTime();
this.startTime = Date.now();
};
/**
+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) {
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2014 Google, Inc. http://angularjs.org
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document){
+8 -4
View File
@@ -55,7 +55,8 @@
if (window.WebKitTransitionEvent) {
evnt = new WebKitTransitionEvent(eventType, eventData);
evnt.initEvent(eventType, false, true);
} else {
}
else {
try {
evnt = new TransitionEvent(eventType, eventData);
}
@@ -64,11 +65,13 @@
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);
}
@@ -77,7 +80,8 @@
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
} else {
}
else {
evnt = document.createEvent('MouseEvents');
x = x || 0;
y = y || 0;
+1 -2
View File
@@ -26,12 +26,11 @@
"manualUppercase": false,
"isArrayLike": false,
"forEach": false,
"sortedKeys": false,
"reverseParams": false,
"nextUid": false,
"setHashKey": false,
"extend": false,
"int": false,
"toInt": false,
"inherit": false,
"noop": false,
"identity": false,
-27
View File
@@ -699,13 +699,6 @@ describe('angular', function() {
});
describe('sortedKeys', function() {
it('should collect keys from object', function() {
expect(sortedKeys({c:0, b:0, a:0})).toEqual(['a', 'b', 'c']);
});
});
describe('encodeUriSegment', function() {
it('should correctly encode uri segment and not encode chars defined as pchar set in rfc3986',
function() {
@@ -1081,26 +1074,6 @@ 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!';
+3 -7
View File
@@ -238,11 +238,7 @@ describe('injector', function() {
it('should publish annotate API', function() {
expect(angular.mock.$$annotate).toBe(annotate);
spyOn(angular.mock, '$$annotate').andCallThrough();
function fn() {}
injector.annotate(fn);
expect(angular.mock.$$annotate).toHaveBeenCalledWith(fn);
expect(injector.annotate).toBe(annotate);
});
});
@@ -976,7 +972,7 @@ describe('strict-di injector', function() {
});
});
inject(function($injector) {
expect(function() {
expect (function() {
$injector.invoke(function($test2) {});
}).toThrowMinErr('$injector', 'strictdi');
});
@@ -990,7 +986,7 @@ describe('strict-di injector', function() {
});
});
inject(function($injector) {
expect(function() {
expect (function() {
$injector.invoke(['$test', function($test) {}]);
}).toThrowMinErr('$injector', 'strictdi');
});
+2 -12
View File
@@ -74,19 +74,9 @@ afterEach(function() {
// copied from Angular.js
// we need these two methods here so that we can run module tests with wrapped angular.js
function sortedKeys(obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys.sort();
}
// we need this method here so that we can run module tests with wrapped angular.js
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj);
var keys = Object.keys(obj).sort();
for (var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
+7
View File
@@ -97,4 +97,11 @@ describe('minErr', function() {
var typeMinErr = minErr('type', TypeError);
expect(typeMinErr('acode', 'aproblem') instanceof TypeError).toBe(true);
});
it('should include a properly formatted error reference URL in the message', function() {
// to avoid maintaining the root URL in two locations, we only validate the parameters
expect(testError('acode', 'aproblem', 'a', 'b', 'value with space').message)
.toMatch(/^[\s\S]*\?p0=a&p1=b&p2=value%20with%20space$/);
});
});
+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');
});
+19 -59
View File
@@ -423,7 +423,8 @@ 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');
}));
@@ -606,7 +607,8 @@ 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');
}));
});
@@ -807,7 +809,8 @@ 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');
@@ -815,7 +818,8 @@ 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');
@@ -824,7 +828,8 @@ 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);
@@ -836,7 +841,8 @@ 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);
@@ -1109,29 +1115,6 @@ 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");
});
});
});
@@ -2051,32 +2034,6 @@ 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");
});
});
});
@@ -4653,7 +4610,8 @@ 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;');
@@ -4938,7 +4896,8 @@ 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');
@@ -5769,7 +5728,8 @@ 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;');
@@ -6105,7 +6065,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); });
});
}
+1 -42
View File
@@ -90,16 +90,7 @@ 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__`.");
});
}));
});
@@ -177,37 +168,5 @@ 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__`.");
});
});
});
+16
View File
@@ -42,6 +42,22 @@ describe('boolean attr directives', function() {
}));
it('should not bind checked when ngModel is present', inject(function($rootScope, $compile) {
// test for https://github.com/angular/angular.js/issues/10662
element = $compile('<input type="checkbox" ng-model="value" ng-false-value="\'false\'" ' +
'ng-true-value="\'true\'" ng-checked="value" />')($rootScope);
$rootScope.value = 'true';
$rootScope.$digest();
expect(element[0].checked).toBe(true);
browserTrigger(element, 'click');
expect(element[0].checked).toBe(false);
expect($rootScope.value).toBe('false');
browserTrigger(element, 'click');
expect(element[0].checked).toBe(true);
expect($rootScope.value).toBe('true');
}));
it('should bind selected', inject(function($rootScope, $compile) {
element = $compile('<select><option value=""></option><option ng-selected="isSelected">Greetings!</option></select>')($rootScope);
jqLite(document.body).append(element);
+4 -117
View File
@@ -1641,9 +1641,9 @@ describe('input', function() {
});
});
describe('max', function() {
describe('max', function() {
it('should invalidate', function() {
it('should invalidate', function() {
var inputElm = helper.compileInput('<input type="date" ng-model="value" name="alias" max="2019-01-01" />');
helper.changeInputValueTo('2019-12-31');
expect(inputElm).toBeInvalid();
@@ -2169,7 +2169,7 @@ describe('input', function() {
var value = 0;
var inputElm = helper.compileInput('<input type="number" ng-model="value" ng-minlength="min" attr-capture />');
helper.attrs.$observe('minlength', function(v) {
value = int(helper.attrs.minlength);
value = toInt(helper.attrs.minlength);
});
$rootScope.$apply(function() {
@@ -2215,7 +2215,7 @@ describe('input', function() {
var value = 0;
var inputElm = helper.compileInput('<input type="number" ng-model="value" ng-maxlength="max" attr-capture />');
helper.attrs.$observe('maxlength', function(v) {
value = int(helper.attrs.maxlength);
value = toInt(helper.attrs.maxlength);
});
$rootScope.$apply(function() {
@@ -2622,116 +2622,3 @@ describe('input', function() {
});
});
});
describe('NgModel animations', function() {
beforeEach(module('ngAnimateMock'));
function findElementAnimations(element, queue) {
var node = element[0];
var animations = [];
for (var i = 0; i < queue.length; i++) {
var animation = queue[i];
if (animation.element[0] == node) {
animations.push(animation);
}
}
return animations;
}
function assertValidAnimation(animation, event, classNameA, classNameB) {
expect(animation.event).toBe(event);
expect(animation.args[1]).toBe(classNameA);
if (classNameB) expect(animation.args[2]).toBe(classNameB);
}
var doc, input, scope, model;
beforeEach(inject(function($rootScope, $compile, $rootElement, $animate) {
scope = $rootScope.$new();
doc = jqLite('<form name="myForm">' +
' <input type="text" ng-model="input" name="myInput" />' +
'</form>');
$rootElement.append(doc);
$compile(doc)(scope);
$animate.queue = [];
input = doc.find('input');
model = scope.myForm.myInput;
}));
afterEach(function() {
dealoc(input);
});
it('should trigger an animation when invalid', inject(function($animate) {
model.$setValidity('required', false);
var animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'removeClass', 'ng-valid');
assertValidAnimation(animations[1], 'addClass', 'ng-invalid');
assertValidAnimation(animations[2], 'addClass', 'ng-invalid-required');
}));
it('should trigger an animation when valid', inject(function($animate) {
model.$setValidity('required', false);
$animate.queue = [];
model.$setValidity('required', true);
var animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'addClass', 'ng-valid');
assertValidAnimation(animations[1], 'removeClass', 'ng-invalid');
assertValidAnimation(animations[2], 'addClass', 'ng-valid-required');
assertValidAnimation(animations[3], 'removeClass', 'ng-invalid-required');
}));
it('should trigger an animation when dirty', inject(function($animate) {
model.$setViewValue('some dirty value');
var animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'removeClass', 'ng-pristine');
assertValidAnimation(animations[1], 'addClass', 'ng-dirty');
}));
it('should trigger an animation when pristine', inject(function($animate) {
model.$setPristine();
var animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'removeClass', 'ng-dirty');
assertValidAnimation(animations[1], 'addClass', 'ng-pristine');
}));
it('should trigger an animation when untouched', inject(function($animate) {
model.$setUntouched();
var animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'setClass', 'ng-untouched');
expect(animations[0].args[2]).toBe('ng-touched');
}));
it('should trigger an animation when touched', inject(function($animate) {
model.$setTouched();
var animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'setClass', 'ng-touched', 'ng-untouched');
expect(animations[0].args[2]).toBe('ng-untouched');
}));
it('should trigger custom errors as addClass/removeClass when invalid/valid', inject(function($animate) {
model.$setValidity('custom-error', false);
var animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'removeClass', 'ng-valid');
assertValidAnimation(animations[1], 'addClass', 'ng-invalid');
assertValidAnimation(animations[2], 'addClass', 'ng-invalid-custom-error');
$animate.queue = [];
model.$setValidity('custom-error', true);
animations = findElementAnimations(input, $animate.queue);
assertValidAnimation(animations[0], 'addClass', 'ng-valid');
assertValidAnimation(animations[1], 'removeClass', 'ng-invalid');
assertValidAnimation(animations[2], 'addClass', 'ng-valid-custom-error');
assertValidAnimation(animations[3], 'removeClass', 'ng-invalid-custom-error');
}));
});
+10
View File
@@ -88,6 +88,16 @@ describe('ngClass', function() {
}));
it('should support adding multiple classes via a space delimited string inside an array', inject(function($rootScope, $compile) {
element = $compile('<div class="existing" ng-class="[\'A B\', \'C\']"></div>')($rootScope);
$rootScope.$digest();
expect(element.hasClass('existing')).toBeTruthy();
expect(element.hasClass('A')).toBeTruthy();
expect(element.hasClass('B')).toBeTruthy();
expect(element.hasClass('C')).toBeTruthy();
}));
it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) {
element = $compile('<div class="existing" ng-class="dynClass"></div>')($rootScope);
$rootScope.dynClass = 'A';
+28
View File
@@ -1733,6 +1733,34 @@ describe('ngModelOptions attributes', function() {
});
it('should allow sharing options between multiple inputs', function() {
$rootScope.options = {updateOn: 'default'};
var inputElm = helper.compileInput(
'<input type="text" ng-model="name1" name="alias1" ' +
'ng-model-options="options"' +
'/>' +
'<input type="text" ng-model="name2" name="alias2" ' +
'ng-model-options="options"' +
'/>');
helper.changeGivenInputTo(inputElm.eq(0), 'a');
helper.changeGivenInputTo(inputElm.eq(1), 'b');
expect($rootScope.name1).toEqual('a');
expect($rootScope.name2).toEqual('b');
});
it('should hold a copy of the options object', function() {
$rootScope.options = {updateOn: 'default'};
var inputElm = helper.compileInput(
'<input type="text" ng-model="name" name="alias" ' +
'ng-model-options="options"' +
'/>');
expect($rootScope.options).toEqual({updateOn: 'default'});
expect($rootScope.form.alias.$options).not.toBe($rootScope.options);
});
it('should allow overriding the model update trigger event on checkboxes', function() {
var inputElm = helper.compileInput(
'<input type="checkbox" ng-model="checkbox" ' +
File diff suppressed because it is too large Load Diff
+142 -39
View File
@@ -83,52 +83,64 @@ describe('ngPluralize', function() {
}));
it('should show single/plural strings with mal-formed inputs', inject(function($rootScope) {
$rootScope.email = '';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
it('should show single/plural strings with mal-formed inputs', inject(
function($log, $rootScope) {
$rootScope.email = '';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
expect($log.debug.logs.shift()).toEqual([
"ngPluralize: no rule defined for 'NaN' in {" +
"'-1': 'You have negative email. Whohoo!'," +
"'0': 'You have no new email'," +
"'one': 'You have one new email'," +
"'other': 'You have {} new emails'}"
]);
expect($log.debug.logs.shift()).toEqual([
"ngPluralize: no rule defined for 'NaN' in undefined"
]);
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = undefined;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = undefined;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = 'a3';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = 'a3';
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
$rootScope.email = '011';
$rootScope.$digest();
expect(element.text()).toBe('You have 11 new emails');
expect(elementAlt.text()).toBe('You have 11 new emails');
$rootScope.email = '011';
$rootScope.$digest();
expect(element.text()).toBe('You have 11 new emails');
expect(elementAlt.text()).toBe('You have 11 new emails');
$rootScope.email = '-011';
$rootScope.$digest();
expect(element.text()).toBe('You have -11 new emails');
expect(elementAlt.text()).toBe('You have -11 new emails');
$rootScope.email = '-011';
$rootScope.$digest();
expect(element.text()).toBe('You have -11 new emails');
expect(elementAlt.text()).toBe('You have -11 new emails');
$rootScope.email = '1fff';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
$rootScope.email = '1fff';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
$rootScope.email = '0aa22';
$rootScope.$digest();
expect(element.text()).toBe('You have no new email');
expect(elementAlt.text()).toBe('You have no new email');
$rootScope.email = '0aa22';
$rootScope.$digest();
expect(element.text()).toBe('You have no new email');
expect(elementAlt.text()).toBe('You have no new email');
$rootScope.email = '000001';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
}));
$rootScope.email = '000001';
$rootScope.$digest();
expect(element.text()).toBe('You have one new email');
expect(elementAlt.text()).toBe('You have one new email');
}
));
});
@@ -144,8 +156,90 @@ describe('ngPluralize', function() {
$rootScope.$digest();
expect(element.text()).toBe('');
}));
it('should be able to specify a message for null/undefined values', inject(
function($compile, $rootScope) {
element = $compile(
'<ng:pluralize count="email"' +
"when=\"{'NaN': 'Unspecified email count'," +
"'0': ''," +
"'one': 'Some text'," +
"'other': 'Some text'}\">" +
'</ng:pluralize>')($rootScope);
$rootScope.email = '0';
$rootScope.$digest();
expect(element.text()).toBe('');
$rootScope.email = undefined;
$rootScope.$digest();
expect(element.text()).toBe('Unspecified email count');
$rootScope.email = '1';
$rootScope.$digest();
expect(element.text()).toBe('Some text');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('Unspecified email count');
}));
});
describe('undefined rule cases', function() {
var $locale, $log;
beforeEach(inject(function(_$locale_, _$log_) {
$locale = _$locale_;
$log = _$log_;
}));
afterEach(inject(function($log) {
$log.reset();
}));
it('should generate a warning when being asked to use a rule that is not defined',
inject(function($rootScope, $compile) {
element = $compile(
'<ng:pluralize count="email"' +
"when=\"{'0': 'Zero'," +
"'one': 'Some text'," +
"'other': 'Some text'}\">" +
'</ng:pluralize>')($rootScope);
$locale.pluralCat = function() {return "few";};
$rootScope.email = '3';
expect($log.debug.logs).toEqual([]);
$rootScope.$digest();
expect(element.text()).toBe('');
expect($log.debug.logs.shift())
.toEqual(["ngPluralize: no rule defined for 'few' in {'0': 'Zero','one': 'Some text','other': 'Some text'}"]);
}));
it('should empty the element content when using a rule that is not defined',
inject(function($rootScope, $compile) {
element = $compile(
'<ng:pluralize count="email"' +
"when=\"{'0': 'Zero'," +
"'one': 'Some text'," +
"'other': 'Some text'}\">" +
'</ng:pluralize>')($rootScope);
$locale.pluralCat = function(count) {return count === 1 ? "one" : "few";};
$rootScope.email = '0';
$rootScope.$digest();
expect(element.text()).toBe('Zero');
$rootScope.email = '3';
$rootScope.$digest();
expect(element.text()).toBe('');
$rootScope.email = '1';
$rootScope.$digest();
expect(element.text()).toBe('Some text');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('');
}));
});
describe('deal with pluralized strings with offset', function() {
it('should show single/plural strings with offset', inject(function($rootScope, $compile) {
@@ -192,6 +286,11 @@ describe('ngPluralize', function() {
$rootScope.$digest();
expect(element.text()).toBe('Igor, Misko and 2 other people are viewing.');
expect(elementAlt.text()).toBe('Igor, Misko and 2 other people are viewing.');
$rootScope.viewCount = null;
$rootScope.$digest();
expect(element.text()).toBe('');
expect(elementAlt.text()).toBe('');
}));
});
@@ -245,7 +344,6 @@ describe('ngPluralize', function() {
describe('bind-once', function() {
it('should support for `count` to be a one-time expression',
inject(function($compile, $rootScope) {
element = $compile(
@@ -269,6 +367,11 @@ describe('ngPluralize', function() {
expect(element.text()).toBe('You have 3 new emails');
expect(elementAlt.text()).toBe('You have 3 new emails');
$rootScope.email = null;
$rootScope.$digest();
expect(element.text()).toBe('You have 3 new emails');
expect(elementAlt.text()).toBe('You have 3 new emails');
$rootScope.email = 2;
$rootScope.$digest();
expect(element.text()).toBe('You have 3 new emails');
+20 -19
View File
@@ -163,7 +163,7 @@ describe('ngRepeat', function() {
'</ul>')(scope);
scope.items = {age:20, wealth:20, prodname: "Bingo", dogname: "Bingo", codename: "20"};
scope.$digest();
expect(element.text()).toEqual('age:20|codename:20|dogname:Bingo|prodname:Bingo|wealth:20|');
expect(element.text()).toEqual('age:20|wealth:20|prodname:Bingo|dogname:Bingo|codename:20|');
});
describe('track by', function() {
@@ -589,7 +589,7 @@ describe('ngRepeat', function() {
'</ul>')(scope);
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
scope.$digest();
expect(element.text()).toEqual('frodo:f:0|misko:m:1|shyam:s:2|');
expect(element.text()).toEqual('misko:m:0|shyam:s:1|frodo:f:2|');
});
@@ -658,10 +658,10 @@ describe('ngRepeat', function() {
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
scope.$digest();
expect(element.text()).
toEqual('doug:d:true-false-false|' +
'frodo:f:false-true-false|' +
'misko:m:false-true-false|' +
'shyam:s:false-false-true|');
toEqual('misko:m:true-false-false|' +
'shyam:s:false-true-false|' +
'doug:d:false-true-false|' +
'frodo:f:false-false-true|');
delete scope.items.doug;
delete scope.items.frodo;
@@ -683,15 +683,15 @@ describe('ngRepeat', function() {
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
scope.$digest();
expect(element.text()).
toBe('doug:d:true-false|' +
'frodo:f:false-true|' +
'misko:m:true-false|' +
'shyam:s:false-true|');
toBe('misko:m:true-false|' +
'shyam:s:false-true|' +
'doug:d:true-false|' +
'frodo:f:false-true|');
delete scope.items.frodo;
delete scope.items.shyam;
scope.$digest();
expect(element.text()).toBe('doug:d:true-false|misko:m:false-true|');
expect(element.text()).toBe('misko:m:true-false|doug:d:false-true|');
});
@@ -702,11 +702,12 @@ describe('ngRepeat', function() {
'</ul>')(scope);
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'};
scope.$digest();
expect(element.text()).
toEqual('doug:d:true-false-false|' +
'frodo:f:false-true-false|' +
'misko:m:false-true-false|' +
'shyam:s:false-false-true|');
toEqual('misko:m:true-false-false|' +
'shyam:s:false-true-false|' +
'doug:d:false-true-false|' +
'frodo:f:false-false-true|');
});
@@ -718,10 +719,10 @@ describe('ngRepeat', function() {
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f', '$toBeFilteredOut': 'xxxx'};
scope.$digest();
expect(element.text()).
toEqual('doug:d:true-false|' +
'frodo:f:false-true|' +
'misko:m:true-false|' +
'shyam:s:false-true|');
toEqual('misko:m:true-false|' +
'shyam:s:false-true|' +
'doug:d:true-false|' +
'frodo:f:false-true|');
});
File diff suppressed because it is too large Load Diff
+2 -44
View File
@@ -224,7 +224,7 @@ describe('validators', 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);
value = toInt(helper.attrs.minlength);
});
$rootScope.$apply('min = 5');
@@ -254,20 +254,6 @@ describe('validators', function() {
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');
});
});
@@ -332,7 +318,7 @@ describe('validators', 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);
value = toInt(helper.attrs.maxlength);
});
$rootScope.$apply('max = 10');
@@ -424,20 +410,6 @@ describe('validators', function() {
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');
});
});
@@ -531,19 +503,5 @@ describe('validators', function() {
$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');
});
});
});
+30 -1
View File
@@ -271,7 +271,7 @@ describe('Filter: filter', function() {
expect(filter(items, expr, true).length).toBe(1);
expect(filter(items, expr, true)[0]).toBe(items[0]);
// Inherited function proprties
// Inherited function properties
function Item(text) {
this.text = text;
}
@@ -399,6 +399,35 @@ describe('Filter: filter', function() {
});
it('should throw an error when is not used with an array', function() {
var item = {'not': 'array'};
expect(function() { filter(item, {}); }).
toThrowMinErr('filter', 'notarray', 'Expected array but received: {"not":"array"}');
item = Object.create(null);
expect(function() { filter(item, {}); }).
toThrowMinErr('filter', 'notarray', 'Expected array but received: {}');
item = {
toString: null,
valueOf: null
};
expect(function() { filter(item, {}); }).
toThrowMinErr('filter', 'notarray', 'Expected array but received: {"toString":null,"valueOf":null}');
});
it('should return undefined when the array is undefined', function() {
expect(filter(undefined, {})).toBeUndefined();
});
it('should return null when the value of the array is null', function() {
var item = null;
expect(filter(item, {})).toBe(null);
});
describe('should support comparator', function() {
it('not consider `object === "[object Object]"` in non-strict comparison', function() {
+8 -3
View File
@@ -150,10 +150,10 @@ describe('filters', function() {
expect(number(Number.NaN)).toEqual('');
expect(number({})).toEqual('');
expect(number([])).toEqual('');
expect(number(+Infinity)).toEqual('');
expect(number(-Infinity)).toEqual('');
expect(number(+Infinity)).toEqual('');
expect(number(-Infinity)).toEqual('-∞');
expect(number("1234.5678")).toEqual('1,234.568');
expect(number(1 / 0)).toEqual("");
expect(number(1 / 0)).toEqual('∞');
expect(number(1, 2)).toEqual("1.00");
expect(number(.1, 2)).toEqual("0.10");
expect(number(.01, 2)).toEqual("0.01");
@@ -247,6 +247,11 @@ describe('filters', function() {
expect(date('')).toEqual('');
});
it('should ignore invalid dates', function() {
var invalidDate = new Date('abc');
expect(date(invalidDate)).toBe(invalidDate);
});
it('should do basic filter', function() {
expect(date(noon)).toEqual(date(noon, 'mediumDate'));
expect(date(noon, '')).toEqual(date(noon, 'mediumDate'));
+22 -12
View File
@@ -34,20 +34,30 @@ describe('Filter: limitTo', function() {
});
it('should return an empty array when X cannot be parsed', function() {
expect(limitTo(items, 'bogus')).toEqual([]);
expect(limitTo(items, 'null')).toEqual([]);
expect(limitTo(items, 'undefined')).toEqual([]);
expect(limitTo(items, null)).toEqual([]);
expect(limitTo(items, undefined)).toEqual([]);
it('should return an empty array when X = 0', function() {
expect(limitTo(items, 0)).toEqual([]);
expect(limitTo(items, '0')).toEqual([]);
});
it('should return an empty string when X cannot be parsed', function() {
expect(limitTo(str, 'bogus')).toEqual("");
expect(limitTo(str, 'null')).toEqual("");
expect(limitTo(str, 'undefined')).toEqual("");
expect(limitTo(str, null)).toEqual("");
expect(limitTo(str, undefined)).toEqual("");
it('should return entire array when X cannot be parsed', function() {
expect(limitTo(items, 'bogus')).toEqual(items);
expect(limitTo(items, 'null')).toEqual(items);
expect(limitTo(items, 'undefined')).toEqual(items);
expect(limitTo(items, null)).toEqual(items);
expect(limitTo(items, undefined)).toEqual(items);
});
it('should return an empty string when X = 0', function() {
expect(limitTo(str, 0)).toEqual("");
expect(limitTo(str, '0')).toEqual("");
});
it('should return entire string when X cannot be parsed', function() {
expect(limitTo(str, 'bogus')).toEqual(str);
expect(limitTo(str, 'null')).toEqual(str);
expect(limitTo(str, 'undefined')).toEqual(str);
expect(limitTo(str, null)).toEqual(str);
expect(limitTo(str, undefined)).toEqual(str);
});
+49 -25
View File
@@ -274,63 +274,59 @@ describe('$http', function() {
describe('the instance', function() {
var $httpBackend, $http, $rootScope;
beforeEach(inject(['$rootScope', function($rs) {
beforeEach(inject(['$httpBackend', '$http', '$rootScope', function($hb, $h, $rs) {
$httpBackend = $hb;
$http = $h;
$rootScope = $rs;
spyOn($rootScope, '$apply').andCallThrough();
}]));
beforeEach(inject(['$httpBackend', '$http', function($hb, $h) {
$httpBackend = $hb;
$http = $h;
}]));
it('should throw error if the request configuration is not an object', inject(function($httpBackend, $http) {
it('should throw error if the request configuration is not an object', function() {
expect(function() {
$http('/url');
}).toThrowMinErr('$http','badreq', 'Http request configuration must be an object. Received: /url');
}));
});
it('should send GET requests if no method specified', inject(function($httpBackend, $http) {
it('should send GET requests if no method specified', function() {
$httpBackend.expect('GET', '/url').respond('');
$http({url: '/url'});
}));
});
it('should do basic request', inject(function($httpBackend, $http) {
it('should do basic request', function() {
$httpBackend.expect('GET', '/url').respond('');
$http({url: '/url', method: 'GET'});
}));
});
it('should pass data if specified', inject(function($httpBackend, $http) {
it('should pass data if specified', function() {
$httpBackend.expect('POST', '/url', 'some-data').respond('');
$http({url: '/url', method: 'POST', data: 'some-data'});
}));
});
describe('params', function() {
it('should do basic request with params and encode', inject(function($httpBackend, $http) {
it('should do basic request with params and encode', function() {
$httpBackend.expect('GET', '/url?a%3D=%3F%26&b=2').respond('');
$http({url: '/url', params: {'a=':'?&', b:2}, method: 'GET'});
}));
});
it('should merge params if url contains some already', inject(function($httpBackend, $http) {
it('should merge params if url contains some already', function() {
$httpBackend.expect('GET', '/url?c=3&a=1&b=2').respond('');
$http({url: '/url?c=3', params: {a:1, b:2}, method: 'GET'});
}));
});
it('should jsonify objects in params map', inject(function($httpBackend, $http) {
it('should jsonify objects in params map', function() {
$httpBackend.expect('GET', '/url?a=1&b=%7B%22c%22:3%7D').respond('');
$http({url: '/url', params: {a:1, b:{c:3}}, method: 'GET'});
}));
});
it('should expand arrays in params map', inject(function($httpBackend, $http) {
it('should expand arrays in params map', function() {
$httpBackend.expect('GET', '/url?a=1&a=2&a=3').respond('');
$http({url: '/url', params: {a: [1,2,3]}, method: 'GET'});
}));
});
it('should not encode @ in url params', function() {
@@ -763,7 +759,7 @@ describe('$http', function() {
$httpBackend.flush();
}));
it('should send execute result if header value is function', inject(function() {
it('should send execute result if header value is function', function() {
var headerConfig = {'Accept': function() { return 'Rewritten'; }};
function checkHeaders(headers) {
@@ -783,7 +779,35 @@ describe('$http', function() {
$http({url: '/url', method: 'DELETE', headers: headerConfig});
$httpBackend.flush();
}));
});
it('should expose a config object to header functions', function() {
var config = {
foo: 'Rewritten',
headers: {'Accept': function(config) {
return config.foo;
}}
};
$httpBackend.expect('GET', '/url', undefined, {Accept: 'Rewritten'}).respond('');
$http.get('/url', config);
$httpBackend.flush();
});
it('should not allow modifications to a config object in header functions', function() {
var config = {
headers: {'Accept': function(config) {
config.foo = 'bar';
return 'Rewritten';
}}
};
$httpBackend.expect('GET', '/url', undefined, {Accept: 'Rewritten'}).respond('');
$http.get('/url', config);
$httpBackend.flush();
expect(config.foo).toBeUndefined();
});
it('should check the cache before checking the XSRF cookie', inject(function($browser, $cacheFactory) {
var testCache = $cacheFactory('testCache'),
+2 -12
View File
@@ -673,16 +673,6 @@ 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}));
@@ -1333,7 +1323,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(),
@@ -1870,7 +1860,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,62 +488,6 @@ 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);
@@ -855,12 +799,6 @@ 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() {
@@ -995,14 +933,6 @@ 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() {
+4 -2
View File
@@ -87,9 +87,11 @@ describe('$sniffer', function() {
var ua = $window.navigator.userAgent.toLowerCase();
if (/chrome/i.test(ua) || /safari/i.test(ua) || /webkit/i.test(ua)) {
expectedPrefix = 'Webkit';
} else if (/firefox/i.test(ua)) {
}
else if (/firefox/i.test(ua)) {
expectedPrefix = 'Moz';
} else if (/ie/i.test(ua) || /trident/i.test(ua)) {
}
else if (/ie/i.test(ua) || /trident/i.test(ua)) {
expectedPrefix = 'Ms';
}
expect($sniffer.vendorPrefix).toBe(expectedPrefix);
+4 -3
View File
@@ -17,7 +17,7 @@ describe('$templateRequest', function() {
}));
it('should cache the request to prevent extra downloads',
inject(function($rootScope, $templateRequest, $httpBackend) {
inject(function($rootScope, $templateRequest, $templateCache, $httpBackend) {
$httpBackend.expectGET('tpl.html').respond('matias');
@@ -34,12 +34,13 @@ describe('$templateRequest', function() {
expect(content[0]).toBe('matias');
expect(content[1]).toBe('matias');
expect($templateCache.get('tpl.html')).toBe('matias');
}));
it('should throw an error when the template is not found',
inject(function($rootScope, $templateRequest, $httpBackend) {
$httpBackend.expectGET('tpl.html').respond(404);
$httpBackend.expectGET('tpl.html').respond(404, '', {}, 'Not found');
$templateRequest('tpl.html');
@@ -48,7 +49,7 @@ describe('$templateRequest', function() {
expect(function() {
$rootScope.$digest();
$httpBackend.flush();
}).toThrowMinErr('$compile', 'tpload', 'Failed to load template: tpl.html');
}).toThrowMinErr('$compile', 'tpload', 'Failed to load template: tpl.html (HTTP status: 404 Not found)');
}));
it('should not throw when the template is not found and ignoreRequestError is true',

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