Compare commits

...

225 Commits

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

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

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

Closes #8153
2015-02-23 19:58:33 +01:00
Martin Staffa f6d0ac5bc8 docs(ngDisabled): clarify the explanation of attributes & interpolation
Closes #11032
Closes #11133
2015-02-22 20:14:01 +01:00
Igor Minar 0356d72cd9 chore(travis,grunt): extract the npm install and cache busting logic into install-dependencies.sh
So now we are DRY.

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

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

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

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

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

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

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

We now need to hurry up and finish the socket.io upgrade in karma which was blocked on shrinkwrap issues in Angular that are resolved with the previous few commits in this PR.
2015-02-21 18:17:50 -08:00
Igor Minar f88178323a chore(clean-shrinkwrap): drop from property from the clean shrinkwrap
it usually contains urls to temp directories which are not interesting, the info
we do want to preserve is in the `resolved` property and we do keep that one.
2015-02-21 18:17:50 -08:00
Igor Minar 76edec7f9b chore(clean-shrinkwrap): preserve git+https:// resolved property
previously we thought that git:// was enough, but we also want git+https:// otherwise we can miss important info
in clean shrinkwrap file
2015-02-21 18:17:50 -08:00
Igor Minar 367c7d90d4 chore(npm): update npm dependencies 2015-02-21 18:17:50 -08:00
Igor Minar 750d06bc25 chore(npm/travis): upgrade to npm 2.5 and require it via package.json
currently karma's dependencies don't install on node 0.12 or io.js so we'll just update npm in hope that that
this will mitigate "cb() never called!" erorrs. See: https://travis-ci.org/angular/angular.js/jobs/51474043#L2181
2015-02-21 18:17:50 -08:00
Igor Minar efb74642a1 chore(npm): don't clean npm-shrinkwrap.json instead generate npm-shrinkwrap.clean.json
Previously we would clean up npm-shrinkwarp.json file in order to achieve serialization
stability, which would then allow us to create human readable diffs that allow code reviews
of npm-shrinkwrap to be meaningful.

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

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

The workaround implemented in this commit is not to interfere with npm-shrinkwrap.json file, but instead
preserve the cleaned up version of its content in npm-shrinkwrap.clean.json which can then be used to
produce human readable diffs for code reviews of npm dependency updates.
2015-02-21 18:17:50 -08:00
Jason Bedard ec27ce7198 test($interpolate): ensure constant interpolation watchers are removed
Closes #11057
2015-02-21 19:05:16 +01:00
Hugo Magalhães 46b80654ca fix(ngSanitize): Do not ignore white-listed svg camelCased attributes
Closes #10779
Closes #10990
Closes #11124
2015-02-21 18:54:57 +01:00
Shahar Talmi c67f88b26c doc(filter): support timezones other than UTC
Closes #10999
2015-02-21 13:16:10 +01:00
awdyson cbedff0619 docs($sce): fix typo
Closes #11108
2015-02-21 13:12:45 +01:00
Georgios Kalpakas 5cdefba1b4 docs(limitToFilter): mark the begin param as optional
Closes #11094
2015-02-21 13:09:47 +01:00
bhalajin b299e73130 docs(guide/Services): fix unit test example
Closes #11081
2015-02-21 13:07:42 +01:00
Yousef 16f12c86f6 docs(tutorial/12): fix grammar and punctuation
Closes #11036
Closes #11037
2015-02-21 13:04:09 +01:00
Ahmed Aderopo Alejo a9e02de5e2 docs(minerr/unpr): provide more info on $scope injection errors
Closes #11030
2015-02-21 12:55:12 +01:00
Haowei Zhang 0a8e113542 docs($resource): fix typo
'parametrized' -> 'parameterized'

Closes #11026
2015-02-21 12:43:01 +01:00
Chris Anderson 3f09847b73 docs(guide/concepts): remove scare quotes and so-called
So-called is defined as "commonly named" or "falsely or improperly so named".
The scare quotes are definitely unnecessary, as well.
It makes it sound like things aren't actually called that,
or it hints at sarcasm: "He's the so-called 'mayor', but he never does anything!"

http://en.wikipedia.org/wiki/Scare_quotes

Closes #11018
2015-02-21 12:32:53 +01:00
rwuebker 7a52da6c63 docs(guide/controller): improve wording
Closes #11000
2015-02-21 12:27:58 +01:00
Pawel Kozlowski 5da1256fc2 fix($http): properly access request headers with mixed case
Fixes #10881
Closes #10883
2015-02-21 12:00:41 +01:00
Diego 910de49399 refactor(ngSwitch):delete default directive restriction
delete the directive restriction to avoid code duplication
since the default is already applied

Closes #10987
Closes #11119
2015-02-21 11:21:54 +01:00
Kevin Mayo 7fe139af5a docs(guide/Expressions): add punctuation for clarity
Closes #11107
2015-02-20 19:54:48 +01:00
Igor Minar 507ee2d9ba chore(travis): turn on caching for node_modules and bower_components directories
The cache behavior is documented at http://docs.travis-ci.com/user/caching/

This commit also disabled our custom caching via npm-bundler-deps.sh
2015-02-19 02:41:48 -08:00
Peter Bacon Darwin ef894c87ea fix(ngOptions): ngModel is optional 2015-02-18 13:32:00 +00:00
Peter Bacon Darwin 30b48132e0 fix(select): remove unknown option when model is undefined and empty option is available
Closes #11078
Closes #11092
2015-02-18 13:18:30 +00:00
Stephen Barker da9eac8660 feat(ngOptions): add support for disabling an option
This patch adds support for disabling options based on model values. The
"disable when" syntax allows for listening to changes on those model values,
in order to dynamically enable and disable the options.

The changes prevent disabled options from being written to the selectCtrl
from the model. If a disabled selection is present on the model, normal
unknown or empty functionality kicks in.

Closes #638
Closes #11017
2015-02-18 09:54:54 +00:00
Jason Bedard 5b5228675f perf($compile): replace forEach(controller) with plain loops
Closes #11084
2015-02-17 22:32:22 +00:00
Tamer Aydin aaae3cc416 feat(limitTo): extend the filter to take a beginning index argument
Extend the limitTo filter to take an optional argument for beginning index.
It provides a slice-alike functionality to manipulate the input.

Closes #5355
Closes #10899
2015-02-17 21:45:09 +01:00
Artem 49b54b0d77 docs(ngRepeat): clarify 'as' microsyntax usage
Closes #11087
2015-02-17 19:34:22 +01:00
Adam Bradley f627233312 fix(templateRequest): avoid throwing syntax error in Android 2.3
Android 2.3 throws an `Uncaught SyntaxError: Unexpected token finally`
pointing at this line. Change `.finally` to bracket notation.

Fixes #11089
Closes #11051
Closes #11088
2015-02-17 19:20:20 +01:00
Jason Bedard 75725b44f8 refactor($compile): combining elementControllers and controllers 2015-02-16 12:48:08 +01:00
Jason Bedard fa0aa83937 perf($compile): avoid .data when fetching required controllers
Closes: ##11059
2015-02-16 12:44:37 +01:00
Martin Staffa bd6c04a112 docs($resource): fix list level
Closes #11055
2015-02-13 23:51:57 +01:00
Peter Bacon Darwin 056a317008 fix(ngModel): fix issues when parserName is same as validator key
For $validate(), it is necessary to store the parseError state
in the controller. Otherwise, if the parser name equals a validator
key, $validate() will assume a parse error occured if the validator
is invalid.

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

Fixes #10698
Closes #10850
Closes #11046
2015-02-13 12:34:22 +00:00
Rouven Weßling 27fcca9a27 refactor(ngSanitize): remove workarounds for IE8
Closes #10758
2015-02-13 11:49:34 +00:00
Peter Bacon Darwin 2015ed2341 test(ngSanitize): add tests for decodeEntities 2015-02-13 11:49:34 +00:00
Peter Bacon Darwin 51faaffdbc perf(ngOptions): only watch labels if a display expression is specified
#11052
2015-02-13 11:29:47 +00:00
Julie Ralph 3c6a0e568d chore(ci): make all browserstack tests allowed failures 2015-02-12 13:21:03 -08:00
Julie Ralph cc744412b3 chore(ci): update to use the latest sauce connect (4.3 to 4.3.6) 2015-02-12 13:18:12 -08:00
Matias Niemelä c9a4421fc3 feat(ngMessages): provide support for dynamic message resolution
Prior to this fix it was impossible to apply a binding to a the
ngMessage directive to represent the name of the error. It was also
not possible to use ngRepeat or any other structural directive to
dynamically update the list of messages. This feature patch ensures
that both ngMessages can render expressions and automatically update
when any dynamic message data changes.

BREAKING CHANGE:

The `ngMessagesInclude` attribute is now its own directive and that must
be placed as a **child** element within the element with the ngMessages
directive. (Keep in mind that the former behaviour of the
ngMessageInclude attribute was that all **included** ngMessage template
code was placed at the **bottom** of the element containing the
ngMessages directive; therefore to make this behave in the same way,
place the element containing the ngMessagesInclude directive at the
end of the container containing the ngMessages directive).

```html
<!-- AngularJS 1.3.x -->
<div ng-messages="model.$error" ng-messages-include="remote.html">
  <div ng-message="required">Your message is required</div>
</div>

<!-- AngularJS 1.4.x -->
<div ng-messages="model.$error">
  <div ng-message="required">Your message is required</div>
  <div ng-messages-include="remote.html"></div>
</div>
```

Closes #10036
Closes #9338
2015-02-12 11:04:10 -05:00
Peter Bacon Darwin 732776f5f3 docs(FAQ): update the zipped file size 2015-02-12 11:59:15 +00:00
Tobyee c211e7a5ad fix(input): create max and/or min validator regardless of initial value
Also adds corresponding tests for ngMin / ngMax.

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

Fixes #10389
Fixes #10212
2015-02-10 22:30:13 +01:00
Peter Bacon Darwin c1199fb6b0 chore(CHANGELOG): add release name! 2015-02-09 11:16:39 +00:00
Peter Bacon Darwin 06c39a033e chore(CHANGELOG): update to 1.4.0-beta.4 and 1.3.13 2015-02-09 10:39:40 +00:00
Marcy Sutton 9d53e5a38d fix(ngAria): ensure native controls fire a single click
Closes #10388
Closes #10766
2015-02-07 10:26:21 +00:00
Lucas Galfaso 030a42e79d fix(sanitize): handle newline characters inside <script> for IE
Tweak the regex used match characters inside <script> and <style> to a IE
compatible regex.
2015-02-06 07:17:04 +01:00
Shahar Talmi 898714df9e fix(ngMock): handle cases where injector is created before tests
This caused an exception for people who created an injector before the tests actually began to run. Since the array was initialized only in beforeEach, anyone accessing it before that would throw. This is solved easily but initializing the array immediately.

Closes #10967
2015-02-06 03:32:42 +02:00
Lucas Galfaso 966f6d831f fix($parse): Initialize elements in an array from left to right
When constructing an array, never lazy initialize the elements and build
the array strictly from left to right.
When evaluating the expressions in a function call, never do so lazy.
When evaluating expressions inside object literals, never do so lazy.

Closes: #10968
2015-02-05 11:21:01 +01:00
izeye 28114faff4 docs(misc/Downloading): change AngularJS version to the latest
Closes #10964
2015-02-04 07:53:33 -08:00
Lucas Galfaso cc8755cda6 fix(sanitize): handle newline characters inside special tags
Handle newlines characters when they are present inside <script> and <style> tags.

Closes #10943
2015-02-04 15:26:44 +01:00
Lukas Elmer 7b7b56d36d chore(docs): fix nav scrolling for non-mobile screens
Closes #10936
2015-02-04 13:37:18 +00:00
Morris Singer 9278ae67c6 docs(guide/forms): improve wording
Closes #01937
2015-02-04 13:16:53 +00:00
dtritus a509e9aa14 fix($location): prevent page reload if initial url has empty hash at the end
If initial url has empty hash at the end, $location replaces it with url
without hash causing an unwanted page reload.

Closes #10397
Closes #10960
2015-02-04 12:48:55 +00:00
Michel Boudreau 09ee82d84d feat(ng-jq): adds the ability to force jqLite or a specific jQuery version
Adds the ability to specify that jqLite should be used or to use a specific jQuery version
2015-02-04 11:01:14 +01:00
Peter Bacon Darwin c1cf053f37 docs(CHANGELOG): update changelog for 1.4.0-beta.3 and 1.3.12 2015-02-03 20:51:30 +00:00
Lucas Galfaso 2e5a7e52a0 fix($parse): handle null targets at assign
When assigning to a sub property of a property that its value is `null`
then write an empty object to the property that will contain the sub-property
2015-02-03 19:46:22 +01:00
Peter Bacon Darwin 5dfa630555 docs(CHANGELOG): update changelog for 1.4.0-beta.3 and 1.3.12 2015-02-02 20:27:32 +00:00
Peter Bacon Darwin 452d1cd66e chore(compare-master-to-stable): support beta release versions 2015-02-02 19:56:27 +00:00
Shahar Talmi c6d8512a1d feat(filter): support conversion to timezone other than UTC
Closes #10858
2015-02-02 14:57:42 +00:00
Richard Zschech 8a2c80ce7f style(*) add curly braces to multiline if and for statements
Closes #10865
2015-02-02 14:48:55 +00:00
Chris 2f3633d68f docs(guide): update "AngularJS" book link
Added latest 2014 revision "AngularJS: Up and Running" with Amazon link.
"AngularJS" was previous 2013 version.

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

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

Closes #10926
2015-02-02 11:49:14 +00:00
Lucas Galfaso 0d424263ea refactor($parse): new and more performant $parse
Change the way parse works from the old mechanism to a multiple stages
parsing and code generation. The new parse is a four stages parsing
* Lexer
* AST building
* AST processing
* Cacheing, one-time binding and `$watch` optimizations

The Lexer phase remains unchanged.

AST building phase follows Mozilla Parse API [1] and generates an AST that
is compatible. The only exception was needed for `filters` as JavaScript
does not support filters, in this case, a filter is transformed into a
`CallExpression` that has an extra property named `filter` with the value
of `true`.

The AST processing phase transforms the AST into a function that can be
executed to evaluate the expression. The logic for expressions remains
unchanged. The AST processing phase works in two different ways depending
if csp is enabled or disabled. If csp is enabled, the processing phase
returns pre-generated function that interpret specific parts of the AST.
When csp is disabled, then the entire expression is compiled into a single
function that is later evaluated using `Function`. In both cases, the
returning function has the properties `constant`, `literal` and `inputs`
as in the previous implementation. These are used in the next phase to
perform different optimizations.

The cacheing, one-time binding and `$watch` optimizations phase remains
mostly unchanged.

[1] https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API
2015-01-31 11:19:09 +01:00
Peter Bacon Darwin 2003fcf0de chore(version-info): silence the HTTP curl to get the cdn version 2015-01-30 21:55:46 +00:00
Peter Bacon Darwin 3130a82b21 chore(version-info): remove console.logs and fix code style 2015-01-30 21:25:14 +00:00
Caitlin Potter 6a38dbfd3c fix($compile): do not initialize optional '&' binding if attribute not specified
BREAKING CHANGE:

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.

Closes #6404
Closes #9216
2015-01-30 21:25:14 +00:00
Henry Zhu 30e5b52344 style(*): add jscs rule disallowKeywordsOnNewLine: "else" 2015-01-30 12:46:55 +00:00
Henry Zhu ebde4681bd style(*): add jscs rule requireSpaceBeforeKeywords
Closes #10772
2015-01-30 12:36:19 +00:00
Henry Zhu ad68a41e88 style(*): add jscs rule requireSpacesInForStatement
Closes #10772
2015-01-30 12:36:19 +00:00
Henry Zhu 1f3ab484a7 style(*): add jscs rule disallowSpacesInCallExpression
Closes #10772
2015-01-30 12:36:18 +00:00
Henry Zhu d9498a173c style(*): add jscs rule disallowKeywordsOnNewLine: "else"
Closes #10772
2015-01-30 12:36:18 +00:00
Peter Bacon Darwin 29ad3b7f36 chore(npm): update grunt-jscs to 1.2.0 (jscs to 1.10.0), fix styles
Closes #10772
2015-01-30 12:36:18 +00:00
Lucas Galfaso c1500ea775 perf($scope): Add a property $$watchersCount to scope
Add a property $$watchersCount to scope that keeps the number of
watchers in the scope plus all the child scopes. Use this property
when traversing scopes looking for watches

Closes: #5799
2015-01-30 12:29:24 +01:00
Peter Bacon Darwin 4bc89bfe6d chore(doc-gen): update to dgeni-packages 0.10.8
Closes https://github.com/angular/dgeni-packages/pull/105
2015-01-30 10:53:34 +00:00
Rohit Kandhal 54097f63d5 docs(tutorial/step-11): update link to Jasmine matchers
Closes #10909
2015-01-29 22:33:26 +00:00
Ian Young 2264413beb docs(ngHide): use proper selector when overriding the CSS
The animation selector gives the default styles greater specificity that
should be matched when overriding.

Closes #10902
Closes #10913
2015-01-29 22:13:35 +00:00
Martin Staffa 8a6cbb3c94 test(validators): minlength and required must use viewValue in $isEmpty 2015-01-29 23:06:24 +01:00
Martin Staffa bfcf9946e1 fix(validators): maxlength should use viewValue for $isEmpty
Closes #10898
2015-01-29 23:06:23 +01:00
Caitlin Potter b462f5dcf2 docs($compile,$route): reword "controller alias" to clarify
Clarify that these aliases are identifier names used to reference the controller.
2015-01-29 16:48:06 -05:00
Caitlin Potter bb1c379b36 docs($compile): create new error for "missing controller identifier" 2015-01-29 16:48:06 -05:00
Caitlin Potter 62d514b069 fix($compile): respect return value from controller constructor
The return value of the controller constructor is now respected in all cases.

If controllerAs is used, the controller will be re-bound to scope. If bindToController is used,
the previous binding $watches (if any) will be unwatched, and bindings re-installed on the new
controller.
2015-01-29 16:48:06 -05:00
Caitlin Potter 35498d7045 feat($compile): allow using bindToController as object, support both new/isolate scopes
bindToController is now able to be specified as a convenient object notation:

```
bindToController: {
  text: '@text',
  obj: '=obj',
  expr: '&expr'
},
scope: {}
```

It can also be used in conjunction with new scopes, rather than exclusively isolate scopes:

```
bindToController: {
  text: '@text',
  obj: '=obj',
  expr: '&expr'
},
scope: true
```

Closes #10420
Closes #10467
2015-01-29 16:47:30 -05:00
Caitlin Potter 630b80fc00 chore($controller): don't use new for minErr instance
minErr creates a new error anyways, it's not meant to be called as a constructor.
2015-01-29 16:26:54 -05:00
Martin Staffa a80d9449b7 docs(guide/production): clarification in disabling debug data
Closes #10762
2015-01-29 19:59:32 +01:00
Caitlin Potter dda65e992b fix($controller): throw better error when controller expression is bad
Previously, the error was a JS runtime error when trying to access a property of `null`. But, it's
a bit nicer to throw a real error and provide a description of how to fix it. Developer ergonomics
and all that.

Closes #10875
Closes #10910
2015-01-29 12:52:25 -05:00
Justin Schiff aa0f64496a fix(angular.copy): support copying %TypedArray%s
angular.copy can now copy a %TypedArray%s.

Limitations: It is not possible to update the length of a %TypedArray%, so currently an error is thrown
if the destination object is a %TypedArray%. However, it is possible to change values in a typed array,
so in the future this may only be a problem if the length of the source and destination is different.

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

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

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

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

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

Closes #10723
2015-01-29 10:15:45 +00:00
Shahar Talmi 8c46919199 fix(filter): format timezone correctly
This fixes timezone formatting in case UTC timezone is used

Closes #9359
2015-01-29 01:18:22 +02:00
Peter Bacon Darwin d729fcf030 fix(a): don't reload if there is only a name attribute
Closes #6273
Closes #10880
2015-01-28 14:30:51 +00:00
Shahar Talmi 0baa17a3b7 feat(ngMocks): cleanup $inject annotations after each test
this will help in detecting unannotated functions both in apps and angular core in case only part of the tests are run with strictDi
2015-01-28 13:47:46 +00:00
Pablo Villoslada Puigcerber 2ece1c927b docs(notarray): add error example and code blocks with suggested fixes
Add code examples for the error and the suggested fixes.
Followup of #10352.

Closes #10872
2015-01-27 21:58:57 +01:00
Alex Yursha 7602cd5e7e docs(guide/location): replace invalid link
Closes #10882
2015-01-27 21:25:21 +01:00
Jason Bedard 560951e988 refactor($interpolate): move standalone functions to the service scope
Closes #10413
2015-01-27 15:42:11 +01:00
Agrumas 1735d5e8d5 chore(i18n): regenerate locales due to closure library update
This includes change to the currency symbol of Lithuania.

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

Closes #10855
Closes #10856
2015-01-27 10:27:46 +00:00
Caitlin Potter 6cf6a1b975 docs(CHANGELOG.md): remove reverted form change 2015-01-26 18:57:50 -05:00
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
167 changed files with 28999 additions and 11310 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
+9 -2
View File
@@ -1,6 +1,7 @@
{
"excludeFiles": ["src/ngLocale/**"],
"disallowKeywords": ["with"],
"disallowKeywordsOnNewLine": ["else"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleLineStrings": true,
"disallowNewlineBeforeBlockStatements": true,
@@ -11,6 +12,7 @@
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInCallExpression": true,
"disallowSpacesInFunctionDeclaration": {
"beforeOpeningRoundBrace": true
},
@@ -18,6 +20,11 @@
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"requireSpaceBeforeKeywords": [
"else",
"while",
"catch"
],
"disallowSpacesInsideParentheses": true,
"disallowTrailingComma": true,
"disallowTrailingWhitespace": true,
@@ -33,9 +40,9 @@
"afterConsequent": true,
"beforeAlternate": true
},
"requireSpacesInForStatement": true,
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"validateLineBreaks": "LF",
"validateParameterSeparator": ", "
"validateLineBreaks": "LF"
}
+1 -1
View File
@@ -4,10 +4,10 @@
// that correct the existing code base issues and make the new check pass.
{
"validateParameterSeparator": ", ", // Re-assert this rule when JSCS allows multiple spaces
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"disallowKeywordsOnNewLine": ["else"],
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
+20 -2
View File
@@ -2,6 +2,12 @@ language: node_js
node_js:
- '0.10'
cache:
directories:
- node_modules
- bower_components
- docs/bower_components
branches:
except:
- /^g3_.*$/
@@ -9,9 +15,11 @@ branches:
env:
matrix:
- JOB=unit BROWSER_PROVIDER=saucelabs
- JOB=docs-e2e BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
- JOB=unit BROWSER_PROVIDER=browserstack
- JOB=docs-e2e BROWSER_PROVIDER=browserstack
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack
global:
@@ -22,14 +30,24 @@ env:
- LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/browsersprovider-tunnel-ready
matrix:
allow_failures:
- env: "JOB=unit BROWSER_PROVIDER=browserstack"
- env: "JOB=docs-e2e BROWSER_PROVIDER=browserstack"
- env: "JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=browserstack"
- env: "JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=browserstack"
install:
# Check the size of caches
- du -sh ./node_modules ./bower_components/ ./docs/bower_components/ || true
# - npm config set registry http://23.251.144.68
# Disable the spinner, it looks bad on Travis
- npm config set spin false
# Log HTTP requests
- npm config set loglevel http
- time ./scripts/travis/npm-bundle-deps.sh
- time npm install
- npm install -g npm@2.5
# Instal npm dependecies and ensure that npm cache is not stale
- scripts/npm/install-dependencies.sh
before_script:
- mkdir -p $LOGS_DIR
+435
View File
@@ -1,3 +1,438 @@
<a name="1.4.0-beta.4"></a>
# 1.4.0-beta.4 overlyexplosive-poprocks (2015-02-09)
## Bug Fixes
- **$location:** prevent page reload if initial url has empty hash at the end
([a509e9aa](https://github.com/angular/angular.js/commit/a509e9aa149d0f88cc39f703d539f7ffd4cd6103),
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
- **$parse:** Initialize elements in an array from left to right
([966f6d83](https://github.com/angular/angular.js/commit/966f6d831f9469a917601f9a10604612cd7bd792))
- **ngAria:** ensure native controls fire a single click
([9d53e5a3](https://github.com/angular/angular.js/commit/9d53e5a38dd369dec82d82e13e078df3d6054c8a),
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
- **ngMock:** handle cases where injector is created before tests
([898714df](https://github.com/angular/angular.js/commit/898714df9ea38f9ef700015ced5ddea52f096b77),
[#10967](https://github.com/angular/angular.js/issues/10967))
- **sanitize:** handle newline characters inside special tags
([cc8755cd](https://github.com/angular/angular.js/commit/cc8755cda6efda0b52954388e8a8d5306e4bfbca),
[030a42e7](https://github.com/angular/angular.js/commit/030a42e79dec8a4bb73053762f7a54d797a058f6)
[#10943](https://github.com/angular/angular.js/issues/10943))
## Features
- **ng-jq:** adds the ability to force jqLite or a specific jQuery version
([09ee82d8](https://github.com/angular/angular.js/commit/09ee82d84dcbea4a6e8d85903af82dcd087a78a7))
<a name="1.3.13"></a>
# 1.3.13 meticulous-riffleshuffle (2015-02-09)
## Bug Fixes
- **$location:** prevent page reload if initial url has empty hash at the end
([4b3a590b](https://github.com/angular/angular.js/commit/4b3a590b009d7fdceda7f52e7ba0352a271b3256),
[#10397](https://github.com/angular/angular.js/issues/10397), [#10960](https://github.com/angular/angular.js/issues/10960))
- **ngAria:** ensure native controls fire a single click
([69ee593f](https://github.com/angular/angular.js/commit/69ee593fd2cb5f1d7757efbe6b256e4458752fd7),
[#10388](https://github.com/angular/angular.js/issues/10388), [#10766](https://github.com/angular/angular.js/issues/10766))
- **ngMock:** handle cases where injector is created before tests
([39ddef68](https://github.com/angular/angular.js/commit/39ddef682971d3b7282bf9d08f6eaf97b7f4bca4),
[#10967](https://github.com/angular/angular.js/issues/10967))
- **sanitize:** handle newline characters inside special tags
([11aedbd7](https://github.com/angular/angular.js/commit/11aedbd741ccddba060a9805adba1779391731da),
[ce49d4d6](https://github.com/angular/angular.js/commit/ce49d4d61bd02464b6c6376af8048f6eb09330a8)
[#10943](https://github.com/angular/angular.js/issues/10943))
<a name="1.4.0-beta.3"></a>
# 1.4.0-beta.3 substance-mimicry (2015-02-02)
## Bug Fixes
- **$compile:**
- do not initialize optional '&' binding if attribute not specified
([6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
[#6404](https://github.com/angular/angular.js/issues/6404), [#9216](https://github.com/angular/angular.js/issues/9216))
- respect return value from controller constructor
([62d514b0](https://github.com/angular/angular.js/commit/62d514b06937cc7dd86e973ea11165c88343b42d))
- **$controller:** throw better error when controller expression is bad
([dda65e99](https://github.com/angular/angular.js/commit/dda65e992b72044c0fa0c8f5f33184028c0e3ad7),
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
- **$parse:**
- handle null targets at assign
([2e5a7e52](https://github.com/angular/angular.js/commit/2e5a7e52a0385575bbb55a801471b009afafeca3))
- remove references to last arguments to a fn call
([e61eae1b](https://github.com/angular/angular.js/commit/e61eae1b1f2351c51bcfe4142749a4e68a2806ff),
[#10894](https://github.com/angular/angular.js/issues/10894))
- **a:** don't reload if there is only a name attribute
([d729fcf0](https://github.com/angular/angular.js/commit/d729fcf030be1d3ef37196d36ea3bf3249ee3318),
[#6273](https://github.com/angular/angular.js/issues/6273), [#10880](https://github.com/angular/angular.js/issues/10880))
- **angular.copy:** support copying `TypedArray`s
([aa0f6449](https://github.com/angular/angular.js/commit/aa0f64496a66d2a5d1a4d033f2eb075a8b084a78),
[#10745](https://github.com/angular/angular.js/issues/10745))
- **filter:** format timezone correctly in the case that UTC timezone is used
([8c469191](https://github.com/angular/angular.js/commit/8c46919199090a05634789774124b38983430c76),
[#9359](https://github.com/angular/angular.js/issues/9359))
- **ngRoute:** dont duplicate optional params into query
([27bf2ce4](https://github.com/angular/angular.js/commit/27bf2ce40c5adfb1494d69c9d0ac9cf433834a12),
[#10689](https://github.com/angular/angular.js/issues/10689))
- **ngScenario:** allow ngScenario to handle lazy-loaded and manually bootstrapped applications
([c69caa7b](https://github.com/angular/angular.js/commit/c69caa7beee4e920f8f587eb3e943be99864a14f),
[#10723](https://github.com/angular/angular.js/issues/10723))
- **validators:** maxlength should use viewValue for $isEmpty
([bfcf9946](https://github.com/angular/angular.js/commit/bfcf9946e16d21b55dde50d4d21c71c898b10215),
[#10898](https://github.com/angular/angular.js/issues/10898))
## Features
- **$compile:** allow using bindToController as object, support both new/isolate scopes
([35498d70](https://github.com/angular/angular.js/commit/35498d7045ba9138016464a344e2c145ce5264c1),
[#10420](https://github.com/angular/angular.js/issues/10420), [#10467](https://github.com/angular/angular.js/issues/10467))
- **filter:** support conversion to timezone other than UTC
([c6d8512a](https://github.com/angular/angular.js/commit/c6d8512a1d7345516d1bd9a039d81821b9518bff),
[#10858](https://github.com/angular/angular.js/issues/10858))
- **ngMocks:** cleanup $inject annotations after each test
([0baa17a3](https://github.com/angular/angular.js/commit/0baa17a3b7ad2b242df2b277b81cebdf75b04287))
## Performance Improvements
- **$scope:** Add a property $$watchersCount to scope
([c1500ea7](https://github.com/angular/angular.js/commit/c1500ea775c4cb130088b7d5bb5fb872bda50bae))
- **$parse** new and more performant parser
([0d42426](https://github.com/angular/angular.js/commit/0d424263ead16635afb582affab2b147f8e71626))
## Breaking Changes
- **$compile:** due to [6a38dbfd](https://github.com/angular/angular.js/commit/6a38dbfd3c34c8f9efff503d17eb3cbeb666d422),
Previously, '&' expressions would always set up a function in the isolate scope. Now, if the binding
is marked as optional and the attribute is not specified, no function will be added to the isolate scope.
<a name="1.3.12"></a>
# 1.3.12 outlandish-knitting (2015-02-02)
## Bug Fixes
- **$controller:** throw better error when controller expression is bad
([632b2ddd](https://github.com/angular/angular.js/commit/632b2ddd34c07b3b5a207bd83ca3a5e6e613e63b),
[#10875](https://github.com/angular/angular.js/issues/10875), [#10910](https://github.com/angular/angular.js/issues/10910))
- **$parse:** remove references to last arguments to a fn call
([7caad220](https://github.com/angular/angular.js/commit/7caad2205a6e9927890192a3638f55532bdaaf75),
[#10894](https://github.com/angular/angular.js/issues/10894))
- **ngRoute:** dont duplicate optional params into query
([f41ca4a5](https://github.com/angular/angular.js/commit/f41ca4a53ed53f172fb334911be56e42aad58794),
[#10689](https://github.com/angular/angular.js/issues/10689))
- **ngScenario:** Allow ngScenario to handle lazy-loaded and manually bootstrapped applications
([0bcd0872](https://github.com/angular/angular.js/commit/0bcd0872d8d2e37e6cb7aa5bc5cb0c742b4294f9),
[#10723](https://github.com/angular/angular.js/issues/10723))
- **validators:** maxlength should use viewValue for $isEmpty
([abd8e2a9](https://github.com/angular/angular.js/commit/abd8e2a9eb2d21ac67989c2f7b64c4c6547a1585),
[#10898](https://github.com/angular/angular.js/issues/10898))
## Features
- **ngMocks:** cleanup $inject annotations after each test
([6ec59460](https://github.com/angular/angular.js/commit/6ec5946094ee92b820bbacc886fa2367715e60b4))
<a name="1.4.0-beta.2"></a>
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
## Bug Fixes
- **$location:** don't rewrite when link is shift-clicked
([8b33de6f](https://github.com/angular/angular.js/commit/8b33de6fd0ec0eb785fed697f062763b5c1d8d23),
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
- **$templateRequest:** cache downloaded templates as strings
([b3a9bd3a](https://github.com/angular/angular.js/commit/b3a9bd3ae043e3042ea7ccfe08e3b36a84feb35e),
[#10630](https://github.com/angular/angular.js/issues/10630), [#10646](https://github.com/angular/angular.js/issues/10646))
- **filterFilter:** throw error if input is not an array
([cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
[#9992](https://github.com/angular/angular.js/issues/9992), [#10352](https://github.com/angular/angular.js/issues/10352))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([2958cd30](https://github.com/angular/angular.js/commit/2958cd308b5ebaf223a3e5df3fb5bf0f23408447),
[#10866](https://github.com/angular/angular.js/issues/10866))
- don't add event listener if replaced, ignore event if target is different element
([b146af11](https://github.com/angular/angular.js/commit/b146af11271de8fa4c51c6db87df104269f41a33),
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
- **ngPluralize:** fix wrong text content when count is null/undefined
([3228d3b4](https://github.com/angular/angular.js/commit/3228d3b4991af681e57de5ab079c1e1c11cf35cb),
[#10836](https://github.com/angular/angular.js/issues/10836), [#10841](https://github.com/angular/angular.js/issues/10841))
## Breaking Changes
- **filterFilter:** due to [cea8e751](https://github.com/angular/angular.js/commit/cea8e75144e6910b806b63a6ec2a6d118316fddd),
Previously, the filter was not applied if used with a non array.
Now, it throws an error. This can be worked around by converting an object to an array, using
a filter such as https://github.com/petebacondarwin/angular-toArrayFilter
Closes #9992
Closes #10352
<a name="1.3.11"></a>
# 1.3.11 spiffy-manatee (2015-01-26)
## Bug Fixes
- **$location:** don't rewrite when link is shift-clicked
([939ca37c](https://github.com/angular/angular.js/commit/939ca37cfe5f6fc35b09b6705caabd1fcc3cf9d3),
[#9904](https://github.com/angular/angular.js/issues/9904), [#9906](https://github.com/angular/angular.js/issues/9906))
- **htmlAnchorDirective:**
- remove "element !== target element" check
([779e3f6b](https://github.com/angular/angular.js/commit/779e3f6b5f8d2550e758cb0c5f64187ba8e00e29),
[#10866](https://github.com/angular/angular.js/issues/10866))
- don't add event listener if replaced, ignore event if target is different element
([837a0775](https://github.com/angular/angular.js/commit/837a077578081bbd07863bef85241537d19fa652),
[#4262](https://github.com/angular/angular.js/issues/4262), [#10849](https://github.com/angular/angular.js/issues/10849))
<a name="1.4.0-beta.1"></a>
# 1.4.0-beta.1 trepidatious-salamander (2015-01-20)
## Bug Fixes
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
([0db5b21b](https://github.com/angular/angular.js/commit/0db5b21b1d09431535e0c0bf8ac63d4b5b24d349),
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
- **$compile:** support class directives on SVG elements
([23c8a90d](https://github.com/angular/angular.js/commit/23c8a90d22f7c7b41b5a756b89498ffac828980a),
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
- **form:** clean up success state of controls when they are removed
([2408f2de](https://github.com/angular/angular.js/commit/2408f2ded5ead6e678c241e38ef474c1fadff92b),
[#10509](https://github.com/angular/angular.js/issues/10509))
- **ngController:** allow bound constructor fns as controllers
([d17fbc38](https://github.com/angular/angular.js/commit/d17fbc3862e0a2e646db1222f184dbe663da4a1f),
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
- **ngRepeat:** do not sort object keys alphabetically
([c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
[#6210](https://github.com/angular/angular.js/issues/6210), [#10538](https://github.com/angular/angular.js/issues/10538))
## Features
- **$http:** provide a config object as an argument to header functions
([d435464c](https://github.com/angular/angular.js/commit/d435464c51d3912f56cfc830d86bfc64a1578327),
[#7235](https://github.com/angular/angular.js/issues/7235), [#10622](https://github.com/angular/angular.js/issues/10622))
## Breaking Changes
- **ngRepeat:** due to [c260e738](https://github.com/angular/angular.js/commit/c260e7386391877625eda086480de73e8a0ba921),
Previously, the order of items when using ngRepeat to iterate
over object properties was guaranteed to be consistent by sorting the
keys into alphabetic order.
Now, the order of the items is browser dependent based on the order returned
from iterating over the object using the `for key in obj` syntax.
It seems that browsers generally follow the strategy of providing
keys in the order in which they were defined, although there are exceptions
when keys are deleted and reinstated. See
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues
The best approach is to convert Objects into Arrays by a filter such as
https://github.com/petebacondarwin/angular-toArrayFilter
or some other mechanism, and then sort them manually in the order you need.
Closes #6210
Closes #10538
<a name="1.3.10"></a>
# 1.3.10 heliotropic-sundial (2015-01-20)
## Bug Fixes
- **$animate:** ensure no transitions are applied when an empty inline style object is provided
([9b8df52a](https://github.com/angular/angular.js/commit/9b8df52aa960b9b6288fc150d55ea2e35f56555e),
[#10613](https://github.com/angular/angular.js/issues/10613), [#10770](https://github.com/angular/angular.js/issues/10770))
- **$compile:** support class directives on SVG elements
([7a9e3360](https://github.com/angular/angular.js/commit/7a9e3360284d58197a1fe34de57f5e0f6d1f4a76),
[#10736](https://github.com/angular/angular.js/issues/10736), [#10756](https://github.com/angular/angular.js/issues/10756))
- **form:** clean up success state of controls when they are removed
([cdc7280d](https://github.com/angular/angular.js/commit/cdc7280dd3d5a2ded784c06dd55fe36c2053fb6f),
[#10509](https://github.com/angular/angular.js/issues/10509))
- **ngController:** allow bound constructor fns as controllers
([d015c8a8](https://github.com/angular/angular.js/commit/d015c8a80b28754633c846fc50d11c9437519486),
[#10784](https://github.com/angular/angular.js/issues/10784), [#10790](https://github.com/angular/angular.js/issues/10790))
<a name="1.4.0-beta.0"></a>
# 1.4.0-beta.0 photonic-umbrakinesis (2015-01-13)
## Bug Fixes
- **$location:** support right button click on anchors in firefox
([aa798f12](https://github.com/angular/angular.js/commit/aa798f123658cb78b5581513d26577016195cafe),
[#7984](https://github.com/angular/angular.js/issues/7984))
- **$templateRequest:** propagate HTTP status on failed requests
([e24f22bd](https://github.com/angular/angular.js/commit/e24f22bdb1740388938d58778aa24d307a79a796),
[#10514](https://github.com/angular/angular.js/issues/10514), [#10628](https://github.com/angular/angular.js/issues/10628))
- **dateFilter:** ignore invalid dates
([1334b8c8](https://github.com/angular/angular.js/commit/1334b8c8326b93e0ca016c85516627900c7a9fd3),
[#10640](https://github.com/angular/angular.js/issues/10640))
- **filterFilter:** use isArray() to determine array type
([a01ce6b8](https://github.com/angular/angular.js/commit/a01ce6b81c197b0a4a1057981e8e9c1b74f37587),
[#10621](https://github.com/angular/angular.js/issues/10621))
- **ngChecked:** ensure that ngChecked doesn't interfere with ngModel
([e079111b](https://github.com/angular/angular.js/commit/e079111b33bf36be21c0941718b41cc9ca67bea0),
[#10662](https://github.com/angular/angular.js/issues/10662), [#10664](https://github.com/angular/angular.js/issues/10664))
- **ngClass:** handle multi-class definitions as an element of an array
([e1132f53](https://github.com/angular/angular.js/commit/e1132f53b03a5a71aa9b6eded24d64e3bc83929b),
[#8578](https://github.com/angular/angular.js/issues/8578), [#10651](https://github.com/angular/angular.js/issues/10651))
- **ngModelOptions:** allow sharing options between multiple inputs
([9c9c6b3f](https://github.com/angular/angular.js/commit/9c9c6b3fe4edfe78ae275c413ee3eefb81f1ebf6),
[#10667](https://github.com/angular/angular.js/issues/10667))
- **ngOptions:**
- support one-time binding on the option values
([ba90261b](https://github.com/angular/angular.js/commit/ba90261b7586b519483883800ea876510faf5c21),
[#10687](https://github.com/angular/angular.js/issues/10687), [#10694](https://github.com/angular/angular.js/issues/10694))
- prevent infinite digest if track by expression is stable
([fc21db8a](https://github.com/angular/angular.js/commit/fc21db8a15545fad53124fc941b3c911a8d57067),
[#9464](https://github.com/angular/angular.js/issues/9464))
- update model if selected option is removed
([933591d6](https://github.com/angular/angular.js/commit/933591d69cee2c5580da1d8522ba90a7d924da0e),
[#7736](https://github.com/angular/angular.js/issues/7736))
- ensure that the correct option is selected when options are loaded async
([7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
[#8019](https://github.com/angular/angular.js/issues/8019), [#9714](https://github.com/angular/angular.js/issues/9714), [#10639](https://github.com/angular/angular.js/issues/10639))
- **ngPluralize:** generate a warning when using a not defined rule
([c66b4b6a](https://github.com/angular/angular.js/commit/c66b4b6a133f7215d50c23db516986cfc1f0a985))
## Features
- **$filter:** display Infinity symbol when number is Infinity
([51d67742](https://github.com/angular/angular.js/commit/51d6774286202b55ade402ca097e417e70fd546b),
[#10421](https://github.com/angular/angular.js/issues/10421))
- **$timeout:** allow `fn` to be an optional parameter
([5a603023](https://github.com/angular/angular.js/commit/5a60302389162c6ef45f311c1aaa65a00d538c66),
[#9176](https://github.com/angular/angular.js/issues/9176))
- **limitTo:** ignore limit when invalid
([a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
[#10510](https://github.com/angular/angular.js/issues/10510))
- **ngMock/$exceptionHandler:** log errors when rethrowing
([deb3cb4d](https://github.com/angular/angular.js/commit/deb3cb4daef0054457bd9fb8995829fff0e8f1e4),
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
## Performance Improvements
- **ngStyleDirective:** use $watchCollection
([8928d023](https://github.com/angular/angular.js/commit/8928d0234551a272992d0eccef73b3ad6cb8bfd1),
[#10535](https://github.com/angular/angular.js/issues/10535))
## Breaking Changes
- **limitTo:** due to [a3c3bf33](https://github.com/angular/angular.js/commit/a3c3bf3332e5685dc319c46faef882cb6ac246e1),
limitTo changed behavior when limit value is invalid.
Instead of returning empty object/array it returns unchanged input.
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
When using `ngOptions`: the directive applies a surrogate key as the value of the `<option>` element.
This commit changes the actual string used as the surrogate key. We now store a string that is computed
by calling `hashKey` on the item in the options collection; previously it was the index or key of the
item in the collection.
(This is in keeping with the way that the unknown option value is represented in the select directive.)
Before you might have seen:
```
<select ng-model="x" ng-option="i in items">
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
<option value="4">d</option>
</select>
```
Now it will be something like:
```
<select ng-model="x" ng-option="i in items">
<option value="string:a">a</option>
<option value="string:b">b</option>
<option value="string:c">c</option>
<option value="string:d">d</option>
</select>
```
If your application code relied on this value, which it shouldn't, then you will need to modify your
application to accommodate this. You may find that you can use the `track by` feaure of `ngOptions`
as this provides the ability to specify the key that is stored.
- **ngOptions:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
When iterating over an object's properties using the `(key, value) in obj` syntax
the order of the elements used to be sorted alphabetically. This was an artificial
attempt to create a deterministic ordering since browsers don't guarantee the order.
But in practice this is not what people want and so this change iterates over properties
in the order they are returned by Object.keys(obj), which is almost always the order
in which the properties were defined.
<a name="1.3.9"></a>
# 1.3.9 multidimensional-awareness (2015-01-13)
## Bug Fixes
- **$parse:** allow use of locals in assignments
([86900814](https://github.com/angular/angular.js/commit/869008140a96e0e9e0d9774cc2e5fdd66ada7ba9))
- **filterFilter:** use isArray() to determine array type
([d4b60ada](https://github.com/angular/angular.js/commit/d4b60ada1ecff5afdb3210caa44e149e9f3d4c1b),
[#10621](https://github.com/angular/angular.js/issues/10621))
## Features
- **ngMock/$exceptionHandler:** log errors when rethrowing
([2b97854b](https://github.com/angular/angular.js/commit/2b97854bf4786fe8579974e2b9d6b4adee8a3dc3),
[#10540](https://github.com/angular/angular.js/issues/10540), [#10564](https://github.com/angular/angular.js/issues/10564))
## Performance Improvements
- **ngStyleDirective:** use $watchCollection
([4c8d8ad5](https://github.com/angular/angular.js/commit/4c8d8ad5083d9dd17c0b8480339d5f95943f1b71),
[#10535](https://github.com/angular/angular.js/issues/10535))
<a name="1.3.8"></a>
# 1.3.8 prophetic-narwhal (2014-12-19)
+3 -3
View File
@@ -87,7 +87,7 @@ Before you submit your pull request consider the following guidelines:
that relates to your submission. You don't want to duplicate effort.
* Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull
requests. We cannot accept code without this.
* Make your changes in a new git branch
* Make your changes in a new git branch:
```shell
git checkout -b my-fix-branch master
@@ -107,7 +107,7 @@ Before you submit your pull request consider the following guidelines:
```
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
* Build your changes locally to ensure all the tests pass
* Build your changes locally to ensure all the tests pass:
```shell
grunt test
@@ -120,7 +120,7 @@ Before you submit your pull request consider the following guidelines:
```
* In GitHub, send a pull request to `angular:master`.
* If we suggest changes then
* If we suggest changes then:
* Make the required updates.
* Re-run the Angular test suite to ensure tests are still passing.
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
+10 -6
View File
@@ -17,10 +17,6 @@ module.exports = function(grunt) {
NG_VERSION.cdn = versionInfo.cdnVersion;
var dist = 'angular-'+ NG_VERSION.full;
//global beforeEach
util.init();
//config
grunt.initConfig({
NG_VERSION: NG_VERSION,
@@ -285,6 +281,10 @@ module.exports = function(grunt) {
},
shell: {
"npm-install": {
command: path.normalize('scripts/npm/install-dependencies.sh')
},
"promises-aplus-tests": {
options: {
stdout: false,
@@ -311,14 +311,18 @@ module.exports = function(grunt) {
}
});
// global beforeEach task
if (!process.env.TRAVIS) {
grunt.task.run('shell:npm-install');
}
//alias tasks
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package','test:unit','test:promises-aplus', 'tests:docs', 'test:protractor']);
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['tests:modules']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['tests:jqlite', 'tests:jquery', 'tests:modules']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:modules']);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
+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
+6 -1
View File
@@ -53,6 +53,7 @@ var angularFiles = {
'src/ng/directive/form.js',
'src/ng/directive/input.js',
'src/ng/directive/ngBind.js',
'src/ng/directive/ngChange.js',
'src/ng/directive/ngClass.js',
'src/ng/directive/ngCloak.js',
'src/ng/directive/ngController.js',
@@ -61,7 +62,10 @@ var angularFiles = {
'src/ng/directive/ngIf.js',
'src/ng/directive/ngInclude.js',
'src/ng/directive/ngInit.js',
'src/ng/directive/ngList.js',
'src/ng/directive/ngModel.js',
'src/ng/directive/ngNonBindable.js',
'src/ng/directive/ngOptions.js',
'src/ng/directive/ngPluralize.js',
'src/ng/directive/ngRepeat.js',
'src/ng/directive/ngShowHide.js',
@@ -70,7 +74,8 @@ var angularFiles = {
'src/ng/directive/ngTransclude.js',
'src/ng/directive/script.js',
'src/ng/directive/select.js',
'src/ng/directive/style.js'
'src/ng/directive/style.js',
'src/ng/directive/validators.js'
],
'angularLoader': [
+9 -2
View File
@@ -14,6 +14,7 @@
<div>ngBind: <input type="radio" ng-model="benchmarkType" value="ngBind"></div>
<div>ngBindOnce: <input type="radio" ng-model="benchmarkType" value="ngBindOnce"></div>
<div>interpolation: <input type="radio" ng-model="benchmarkType" value="interpolation"></div>
<div>interpolation + bind-once: <input type="radio" ng-model="benchmarkType" value="bindOnceInterpolation"></div>
<div>attribute interpolation: <input type="radio" ng-model="benchmarkType" value="interpolationAttr"></div>
<div>ngBind + fnInvocation: <input type="radio" ng-model="benchmarkType" value="ngBindFn"></div>
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
@@ -35,7 +36,7 @@
</div>
<div ng-switch-when="ngBindOnce">
<h2>baseline binding once</h2>
<div ng-repeat="row in data">
<div ng-repeat="row in ::data">
<span ng-repeat="column in ::row">
<span ng-bind="::column.i"></span>:<span ng-bind="::column.j"></span>|
</span>
@@ -47,6 +48,12 @@
<span ng-repeat="column in row">{{column.i}}:{{column.j}}|</span>
</div>
</div>
<div ng-switch-when="bindOnceInterpolation">
<h2>baseline one-time interpolation</h2>
<div ng-repeat="row in ::data">
<span ng-repeat="column in ::row">{{::column.i}}:{{::column.j}}|</span>
</div>
</div>
<div ng-switch-when="interpolationAttr">
<h2>attribute interpolation</h2>
<div ng-repeat="row in data">
@@ -80,4 +87,4 @@
</ng-switch>
</div>
</div>
</div>
</div>
+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>
+2 -2
View File
@@ -103,10 +103,10 @@ then(function (tags) {
sort(semver.rcompare);
}).
then(function (tags) {
var major = tags[0].split('.')[0] + '.x';
var major = tags[0].split('.')[0];
return tags.
filter(function (ver) {
return semver.satisfies(ver, major);
return semver(ver).major == major;
});
}).
then(function (tags) {
+6 -5
View File
@@ -546,10 +546,10 @@ h4 {
margin-left:10px;
}
.btn:hover {
color:black!important;
.btn:hover, .btn:focus {
color: black!important;
border: 1px solid #ddd!important;
background:white!important;
background: white!important;
}
.view-source, .improve-docs {
@@ -631,6 +631,7 @@ ul.events > li {
}
.main-body-grid .side-navigation {
display:block!important;
padding-bottom:50px;
}
.main-body-grid .side-navigation.ng-hide {
display:none!important;
@@ -656,14 +657,14 @@ ul.events > li {
}
.toc-close {
position: absolute;
bottom: -50px;
bottom: 5px;
left: 50%;
margin-left: -50%;
text-align: center;
padding: 5px;
background: #eee;
border-radius: 5px;
width: 90%;
width: 100%;
border:1px solid #ddd;
box-shadow:0 0 10px #bbb;
}
+11 -7
View File
@@ -1,13 +1,16 @@
angular.module('examples', [])
.factory('formPostData', ['$document', function($document) {
return function(url, fields) {
return function(url, newWindow, fields) {
/**
* Form previously posted to target="_blank", but pop-up blockers were causing this to not work.
* If a user chose to bypass pop-up blocker one time and click the link, they would arrive at
* a new default plnkr, not a plnkr with the desired template.
* If the form posts to target="_blank", pop-up blockers can cause it not to work.
* If a user choses to bypass pop-up blocker one time and click the link, they will arrive at
* a new default plnkr, not a plnkr with the desired template. Given this undesired behavior,
* some may still want to open the plnk in a new window by opting-in via ctrl+click. The
* newWindow param allows for this possibility.
*/
var form = angular.element('<form style="display: none;" method="post" action="' + url + '"></form>');
var target = newWindow ? '_blank' : '_self';
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="' + target + '"></form>');
angular.forEach(fields, function(value, name) {
var input = angular.element('<input type="hidden" name="' + name + '">');
input.attr('value', value);
@@ -21,9 +24,10 @@ angular.module('examples', [])
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
return function(exampleFolder) {
return function(exampleFolder, clickEvent) {
var exampleName = 'AngularJS Example';
var newWindow = clickEvent.ctrlKey || clickEvent.metaKey;
// Load the manifest for the example
$http.get(exampleFolder + '/manifest.json')
@@ -71,7 +75,7 @@ angular.module('examples', [])
postData.private = true;
postData.description = exampleName;
formPostData('http://plnkr.co/edit/?p=preview', postData);
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
});
};
}]);
@@ -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}}">
@@ -2,7 +2,7 @@
is HTML and wrap each line in a <p> - thus breaking the HTML #}
<div>
<a ng-click="openPlunkr('{$ doc.path $}')" class="btn pull-right">
<a ng-click="openPlunkr('{$ doc.path $}', $event)" class="btn pull-right">
<i class="glyphicon glyphicon-edit">&nbsp;</i>
Edit in Plunker</a>
+12
View File
@@ -0,0 +1,12 @@
@ngdoc error
@name $compile:noctrl
@fullName Controller is required.
@description
When using the `bindToController` feature of AngularJS, a directive is required
to have a Controller. A controller may be specified by adding a "controller"
property to the directive definition object. Its value should be either a
string, or an invokable object (a function, or an array whose last element is a
function).
For more information, see the {@link guide/directive directives guide}.
+71
View File
@@ -0,0 +1,71 @@
@ngdoc error
@name $compile:noident
@fullName Controller identifier is required.
@description
When using the `bindToController` feature of AngularJS, a directive is required
to have a Controller identifier, which is initialized in scope with the value of
the controller instance. This can be supplied using the "controllerAs" property
of the directive object, or alternatively by adding " as IDENTIFIER" to the controller
name.
For example, the following directives are valid:
```js
// OKAY, because controller is a string with an identifier component.
directive("okay", function() {
return {
bindToController: true,
controller: "myCtrl as $ctrl"
scope: {
text: "@text"
}
};
});
// OKAY, because the directive uses the controllerAs property to override
// the controller identifier.
directive("okay2", function() {
return {
bindToController: true,
controllerAs: "$ctrl",
controller: function() {
},
scope: {
text: "@text"
}
};
});
```
While the following are invalid:
```js
// BAD, because the controller property is a string with no identifier.
directive("bad", function() {
return {
bindToController: true,
controller: "noIdentCtrl",
scope: {
text: "@text"
}
};
});
// BAD because the controller is not a string (therefore has no identifier),
// and there is no controllerAs property.
directive("bad2", function() {
return {
bindToController: true,
controller: function noControllerAs() {
},
scope: {
text: "@text"
}
};
});
```
@@ -0,0 +1,46 @@
@ngdoc error
@name $controller:ctrlfmt
@fullName Badly formed controller string
@description
This error occurs when {@link ng.$controller $controller} service is called
with a string that does not match the supported controller string formats.
Supported formats:
1. `__name__`
2. `__name__ as __identifier__`
N'either `__name__` or `__identifier__` may contain spaces.
Example of incorrect usage that leads to this error:
```html
<!-- unclosed ng-controller attribute messes up the format -->
<div ng-controller="myController>
```
or
```js
// does not match `__name__` or `__name__ as __identifier__`
var myCtrl = $controller("mY contRoller", { $scope: newScope });
```
or
```js
directive("myDirective", function() {
return {
// does not match `__name__` or `__name__ as __identifier__`
controller: "mY contRoller",
link: function() {}
};
});
```
To fix the examples above, ensure that the controller string matches the supported
formats, and that any html attributes which are used as controller expressions are
closed.
Please consult the {@link ng.$controller $controller} service api docs to learn more.
+12
View File
@@ -69,3 +69,15 @@ angular.module('myModule', [])
```
Use the `$controller` service if you want to instantiate controllers yourself.
Attempting to inject a scope object into anything that's not a controller or a directive,
for example a service, will also throw an `Unknown provider: $scopeProvider <- $scope` error.
This might happen if one mistakenly registers a controller as a service, ex.:
```
angular.module('myModule', [])
.service('MyController', ['$scope', function($scope) {
// This controller throws an unknown provider error because
// a scope object cannot be injected into a service.
}]);
```
+1 -1
View File
@@ -35,7 +35,7 @@ URL of the subcontext:
```html
<head>
<base href="/subapp">
<base href="/subapp/">
...
</head>
```
+1 -1
View File
@@ -35,7 +35,7 @@ var users = [ { name: 'Hank' }, { name: 'Francisco' } ];
$scope.getUsers = function() {
return users;
});
};
```
The maximum number of allowed iterations of the `$digest` cycle is controlled via TTL setting which can be configured via {@link ng.$rootScopeProvider $rootScopeProvider}.
+51
View File
@@ -0,0 +1,51 @@
@ngdoc error
@name filter:notarray
@fullName Not an array
@description
This error occurs when {@link ng.filter filter} is not used with an array:
```html
<input ng-model="search">
<div ng-repeat="(key, value) in myObj | filter:search">
{{ key }} : {{ value }}
</div>
```
Filter must be used with an array so a subset of items can be returned.
The array can be initialized asynchronously and therefore null or undefined won't throw this error.
To filter an object by the value of its properties you can create your own custom filter:
```js
angular.module('customFilter',[])
.filter('custom', function() {
return function(input, search) {
if (!input) return input;
if (!search) return input;
var expected = ('' + search).toLowerCase();
var result = {};
angular.forEach(input, function(value, key) {
var actual = ('' + value).toLowerCase();
if (actual.indexOf(expected) !== -1) {
result[key] = value;
}
});
return result;
}
});
```
That can be used as:
```html
<input ng-model="search">
<div ng-repeat="(key, value) in myObj | custom:search">
{{ key }} : {{ value }}
</div>
```
You could as well convert the object to an array using a filter such as
[toArrayFilter](https://github.com/petebacondarwin/angular-toArrayFilter):
```html
<input ng-model="search">
<div ng-repeat="item in myObj | toArray:false | filter:search">
{{ item }}
</div>
```
+7
View File
@@ -0,0 +1,7 @@
@ngdoc error
@name ng:cpta
@fullName Copying TypedArray
@description
Copying TypedArray's with a destination is not supported because TypedArray
objects can not be mutated, they are fixed length.
@@ -16,6 +16,8 @@ Reserved names include:
- `this`
- `undefined`
- `$parent`
- `$id`
- `$root`
- `$even`
- `$odd`
- `$first`
+2 -2
View File
@@ -165,7 +165,7 @@ encoded.
`$location` service has two configuration modes which control the format of the URL in the browser
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
HTML5 [History API](http://www.w3.org/TR/html5/introduction.html#history-0). Applications use the same API in
[HTML5 History API](https://html.spec.whatwg.org/multipage/browsers.html#the-history-interface). Applications use the same API in
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
facilitate the browser URL change and history management.
@@ -358,7 +358,7 @@ legacy browsers and hashbang links in modern browser:
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
that you can see the differences. These `$location` services are connected to a fake browsers. Each
input represents address bar of the browser.
input represents the address bar of the browser.
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
+9 -5
View File
@@ -37,6 +37,7 @@ Currently, ngAria interfaces with the following directives:
* {@link guide/accessibility#nghide ngHide}
* {@link guide/accessibility#ngclick ngClick}
* {@link guide/accessibility#ngdblclick ngDblClick}
* {@link guide/accessibility#ngmessages ngMessages}
<h2 id="ngmodel">ngModel</h2>
@@ -209,10 +210,14 @@ The default CSS for `ngHide`, the inverse method to `ngShow`, makes ngAria redun
`display: none`. See explanation for {@link guide/accessibility#ngshow ngShow} when overriding the default CSS.
<h2><span id="ngclick">ngClick</span> and <span id="ngdblclick">ngDblclick</span></h2>
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex` if it isn't there already.
Even with this, you must currently still add `ng-keypress` to non-interactive elements such as `div`
or `taco-button` to enable keyboard access. Conversation is currently ongoing about whether ngAria
should also bind `ng-keypress`.
If `ng-click` or `ng-dblclick` is encountered, ngAria will add `tabindex="0"` if it isn't there
already.
For `ng-click`, keypress will also be bound to `div` and `li` elements. You can turn this
functionality on or off with the `bindKeypress` configuration option.
For `ng-dblclick`, you must manually add `ng-keypress` to non-interactive elements such as `div`
or `taco-button` to enable keyboard access.
<h3>Example</h3>
```html
@@ -223,7 +228,6 @@ Becomes:
```html
<div ng-click="toggleMenu()" tabindex="0"></div>
```
*Note: ngAria still requires `ng-keypress` to be added manually to non-native controls like divs.*
<h2 id="ngmessages">ngMessages</h2>
+2 -2
View File
@@ -88,7 +88,7 @@ As a best practice, consider adding an `ng-strict-di` directive on the same elem
```
This will ensure that all services in your application are properly annotated.
See the {@link guide/di#using-strict-dependency-injection dependancy injection strict mode} docs
See the {@link guide/di#using-strict-dependency-injection dependency injection strict mode} docs
for more.
@@ -156,4 +156,4 @@ until `angular.resumeBootstrap()` is called.
`angular.resumeBootstrap()` takes an optional array of modules that
should be added to the original list of modules that the app was
about to be bootstrapped with.
about to be bootstrapped with.
+15 -15
View File
@@ -55,11 +55,11 @@ Try out the Live Preview above, and then let's walk through the example and desc
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding1.png">
This looks like normal HTML, with some new markup. In Angular, a file like this is called a
<a name="template">"{@link templates template}"</a>. When Angular starts your application, it parses and
processes this new markup from the template using the so-called <a name="compiler">"{@link compiler compiler}"</a>.
The loaded, transformed and rendered DOM is then called the <a name="view">"view"</a>.
<a name="template">{@link templates template}</a>. When Angular starts your application, it parses and
processes this new markup from the template using the <a name="compiler">{@link compiler compiler}</a>.
The loaded, transformed and rendered DOM is then called the <a name="view">view</a>.
The first kind of new markup are the so-called <a name="directive">"{@link directive directives}"</a>.
The first kind of new markup are the <a name="directive">{@link directive directives}</a>.
They apply special behavior to attributes or elements in the HTML. In the example above we use the
{@link ng.directive:ngApp `ng-app`} attribute, which is linked to a directive that automatically
initializes our application. Angular also defines a directive for the {@link ng.directive:input `input`}
@@ -75,16 +75,16 @@ stores/updates the value of the input field into/from a variable.
The second kind of new markup are the double curly braces `{{ expression | filter }}`:
When the compiler encounters this markup, it will replace it with the evaluated value of the markup.
An <a name="expression">"{@link expression expression}"</a> in a template is a JavaScript-like code snippet that allows
An <a name="expression">{@link expression expression}</a> in a template is a JavaScript-like code snippet that allows
to read and write variables. Note that those variables are not global variables.
Just like variables in a JavaScript function live in a scope,
Angular provides a <a name="scope">"{@link scope scope}"</a> for the variables accessible to expressions.
The values that are stored in variables on the scope are referred to as the <a name="model">"model"</a>
Angular provides a <a name="scope">{@link scope scope}</a> for the variables accessible to expressions.
The values that are stored in variables on the scope are referred to as the <a name="model">model</a>
in the rest of the documentation.
Applied to the example above, the markup directs Angular to "take the data we got from the input widgets
and multiply them together".
The example above also contains a <a name="filter">"{@link guide/filter filter}"</a>.
The example above also contains a <a name="filter">{@link guide/filter filter}</a>.
A filter formats the value of an expression for display to the user.
In the example above, the filter {@link ng.filter:currency `currency`} formats a number
into an output that looks like money.
@@ -92,7 +92,7 @@ into an output that looks like money.
The important thing in the example is that Angular provides _live_ bindings:
Whenever the input values change, the value of the expressions are automatically
recalculated and the DOM is updated with their values.
The concept behind this is <a name="databinding">"{@link databinding two-way data binding}"</a>.
The concept behind this is <a name="databinding">{@link databinding two-way data binding}</a>.
## Adding UI logic: Controllers
@@ -150,7 +150,7 @@ different currencies and also pay the invoice.
What changed?
First, there is a new JavaScript file that contains a so-called <a name="controller">"{@link controller controller}"</a>.
First, there is a new JavaScript file that contains a <a name="controller">{@link controller controller}</a>.
More exactly, the file contains a constructor function that creates the actual controller instance.
The purpose of controllers is to expose variables and functionality to expressions and directives.
@@ -182,8 +182,8 @@ The following graphic shows how everything works together after we introduced th
## View independent business logic: Services
Right now, the `InvoiceController` contains all logic of our example. When the application grows it
is a good practice to move view independent logic from the controller into a so called
<a name="service">"{@link services service}"</a>, so it can be reused by other parts
is a good practice to move view independent logic from the controller into a
<a name="service">{@link services service}</a>, so it can be reused by other parts
of the application as well. Later on, we could also change that service to load the exchange rates
from the web, e.g. by calling the Yahoo Finance API, without changing the controller.
@@ -255,15 +255,15 @@ We moved the `convertCurrency` function and the definition of the existing curre
into the new file `finance2.js`. But how does the controller
get a hold of the now separated function?
This is where <a name="di">"{@link di Dependency Injection}"</a> comes into play.
This is where <a name="di">{@link di Dependency Injection}</a> comes into play.
Dependency Injection (DI) is a software design pattern that
deals with how objects and functions get created and how they get a hold of their dependencies.
Everything within Angular (directives, filters, controllers,
services, ...) is created and wired using dependency injection. Within Angular,
the DI container is called the <a name="injector">"{@link di injector}"</a>.
the DI container is called the <a name="injector">{@link di injector}</a>.
To use DI, there needs to be a place where all the things that should work together are registered.
In Angular, this is the purpose of the so-called <a name="module">"{@link module modules}"</a>.
In Angular, this is the purpose of the <a name="module">{@link module modules}</a>.
When Angular starts, it will use the configuration of the module with the name defined by the `ng-app` directive,
including the configuration of all modules that this module depends on.
+1 -1
View File
@@ -75,7 +75,7 @@ In order to react to events or execute computation in the view we must provide b
scope. We add behavior to the scope by attaching methods to the `$scope` object. These methods are
then available to be called from the template/view.
The following example uses a Controller to add a method to the scope, which doubles a number:
The following example uses a Controller to add a method, which doubles a number, to the scope:
```js
var myApp = angular.module('myApp',[]);
+14 -4
View File
@@ -751,9 +751,12 @@ own behavior to it.
angular.module('docsIsoFnBindExample', [])
.controller('Controller', ['$scope', '$timeout', function($scope, $timeout) {
$scope.name = 'Tobias';
$scope.hideDialog = function () {
$scope.message = '';
$scope.hideDialog = function (message) {
$scope.message = message;
$scope.dialogIsHidden = true;
$timeout(function () {
$scope.message = '';
$scope.dialogIsHidden = false;
}, 2000);
};
@@ -771,14 +774,15 @@ own behavior to it.
</file>
<file name="index.html">
<div ng-controller="Controller">
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog()">
{{message}}
<my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)">
Check out the contents, {{name}}!
</my-dialog>
</div>
</file>
<file name="my-dialog-close.html">
<div class="alert">
<a href class="close" ng-click="close()">&times;</a>
<a href class="close" ng-click="close({message: 'closing for now'})">&times;</a>
<div ng-transclude></div>
</div>
</file>
@@ -795,9 +799,15 @@ callback functions to directive behaviors.
When the user clicks the `x` in the dialog, the directive's `close` function is called, thanks to
`ng-click.` This call to `close` on the isolated scope actually evaluates the expression
`hideDialog()` in the context of the original scope, thus running `Controller`'s `hideDialog`
`hideDialog(message)` in the context of the original scope, thus running `Controller`'s `hideDialog`
function.
Often it's desirable to pass data from the isolate scope via an expression to the
parent scope, this can be done by passing a map of local variable names and values into the expression
wrapper fn. For example, the hideDialog function takes a message to display when the dialog is hidden.
This is specified in the directive by calling `close({message: 'closing for now'})`. Then the local
variable `message` will be available within the `on-close` expression.
<div class="alert alert-success">
**Best Practice:** use `&attr` in the `scope` option when you want your directive
to expose an API for binding to behaviors.
+3 -3
View File
@@ -297,9 +297,9 @@ then the expression is not fulfilled and will remain watched.
2. If V is not undefined, mark the result of the expression as stable and schedule a task
to deregister the watch for this expression when we exit the digest loop
3. Process the digest loop as normal
4. When digest loop is done and all the values have settled process the queue of watch
deregistration tasks. For each watch to be deregistered check if it still evaluates
to value that is not `undefined`. If that's the case, deregister the watch. Otherwise
4. When digest loop is done and all the values have settled, process the queue of watch
deregistration tasks. For each watch to be deregistered, check if it still evaluates
to a value that is not `undefined`. If that's the case, deregister the watch. Otherwise,
keep dirty-checking the watch in the future digest loops by following the same
algorithm starting from step 1
+8 -7
View File
@@ -6,11 +6,12 @@
Controls (`input`, `select`, `textarea`) are ways for a user to enter data.
A Form is a collection of controls for the purpose of grouping related controls together.
Form and controls provide validation services, so that the user can be notified of invalid input.
This provides a better user experience, because the user gets instant feedback on how to
correct the error. Keep in mind that while client-side validation plays an important role
in providing good user experience, it can easily be circumvented and thus can not be trusted.
Server-side validation is still necessary for a secure application.
Form and controls provide validation services, so that the user can be notified of invalid input
before submitting a form. This provides a better user experience than server-side validation alone
because the user gets instant feedback on how to correct the error. Keep in mind that while
client-side validation plays an important role in providing good user experience, it can easily
be circumvented and thus can not be trusted. Server-side validation is still necessary for a
secure application.
# Simple form
@@ -131,7 +132,7 @@ A form is an instance of {@link form.FormController FormController}.
The form instance can optionally be published into the scope using the `name` attribute.
Similarly, an input control that has the {@link ng.directive:ngModel ngModel} directive holds an
instance of {@link ngModel.NgModelController NgModelController}.Such a control instance
instance of {@link ngModel.NgModelController NgModelController}. Such a control instance
can be published as a property of the form instance using the `name` attribute on the input control.
The name attribute specifies the name of the property on the form instance.
@@ -339,7 +340,7 @@ In the following example we create two directives:
<div>
Username:
<input type="text" ng-model="name" name="name" username />{{name}}<br />
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
<span ng-show="form.name.$pending.username">Checking if this name is available...</span>
<span ng-show="form.name.$error.username">This username is already taken!</span>
</div>
+3 -2
View File
@@ -53,7 +53,7 @@ In Angular applications, you move the job of filling page templates with data fr
## Specific Topics
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Faceb0ok library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
@@ -62,6 +62,7 @@ In Angular applications, you move the job of filling page templates with data fr
## Tools
* **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/)
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
@@ -101,7 +102,7 @@ This is a short list of libraries with specific support and documentation for wo
## Learning Resources
###Books
* [AngularJS](http://www.amazon.com/AngularJS-Brad-Green/dp/1449344852) by Brad Green and Shyam Seshadri
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
+12 -12
View File
@@ -12,7 +12,7 @@ succinctly. Angular's data binding and dependency injection eliminate much of th
would otherwise have to write. And it all happens within the browser, making it
an ideal partner with any server technology.
Angular is what HTML would have been had it been designed for applications. HTML is a great
Angular is what HTML would have been, had it been designed for applications. HTML is a great
declarative language for static documents. It does not contain much in the way of creating
applications, and as a result building web applications is an exercise in *what do I have to do
to trick the browser into doing what I want?*
@@ -28,10 +28,10 @@ The impedance mismatch between dynamic applications and static documents is ofte
Angular takes another approach. It attempts to minimize the impedance mismatch between document
centric HTML and what an application needs by creating new HTML constructs. Angular teaches the
browser new syntax through a construct we call directives. Examples include:
browser new syntax through a construct we call *directives*. Examples include:
* Data binding, as in `{{}}`.
* DOM control structures for repeating/hiding DOM fragments.
* DOM control structures for repeating, showing and hiding DOM fragments.
* Support for forms and form validation.
* Attaching new behavior to DOM elements, such as DOM event handling.
* Grouping of HTML into reusable components.
@@ -42,20 +42,20 @@ browser new syntax through a construct we call directives. Examples include:
Angular is not a single piece in the overall puzzle of building the client-side of a web
application. It handles all of the DOM and AJAX glue code you once wrote by hand and puts it in a
well-defined structure. This makes Angular opinionated about how a CRUD application should be
built. But while it is opinionated, it also tries to make sure that its opinion is just a
starting point you can easily change. Angular comes with the following out-of-the-box:
well-defined structure. This makes Angular opinionated about how a CRUD (Create, Read, Update, Delete)
application should be built. But while it is opinionated, it also tries to make sure that its opinion
is just a starting point you can easily change. Angular comes with the following out-of-the-box:
* Everything you need to build a CRUD app in a cohesive set: data-binding, basic templating
directives, form validation, routing, deep-linking, reusable components, dependency injection.
* Testability story: unit-testing, end-to-end testing, mocks, test harnesses.
* Everything you need to build a CRUD app in a cohesive set: Data-binding, basic templating
directives, form validation, routing, deep-linking, reusable components and dependency injection.
* Testability story: Unit-testing, end-to-end testing, mocks and test harnesses.
* Seed application with directory layout and test scripts as a starting point.
## Angular Sweet Spot
## Angular's sweet spot
Angular simplifies application development by presenting a higher level of abstraction to the
developer. Like any abstraction, it comes at a cost of flexibility. In other words not every app
developer. Like any abstraction, it comes at a cost of flexibility. In other words, not every app
is a good fit for Angular. Angular was built with the CRUD application in mind. Luckily CRUD
applications represent the majority of web applications. To understand what Angular is
good at, though, it helps to understand when an app is not a good fit for Angular.
@@ -78,7 +78,7 @@ expressing business logic.
* It is an excellent idea to decouple the client side of an app from the server side. This
allows development work to progress in parallel, and allows for reuse of both sides.
* It is very helpful indeed if the framework guides developers through the entire journey of
building an app: from designing the UI, through writing the business logic, to testing.
building an app: From designing the UI, through writing the business logic, to testing.
* It is always good to make common tasks trivial and difficult tasks possible.
+43 -5
View File
@@ -15,13 +15,51 @@ which drives many of these changes.
# Migrating from 1.2 to 1.3
## Controllers
Due to [3f2232b5](https://github.com/angular/angular.js/commit/3f2232b5a181512fac23775b1df4a6ebda67d018),
`$controller` will no longer look for controllers on `window`.
The old behavior of looking on `window` for controllers was originally intended
for use in examples, demos, and toy apps. We found that allowing global controller
functions encouraged poor practices, so we resolved to disable this behavior by
default.
To migrate, register your controllers with modules rather than exposing them
as globals:
Before:
```javascript
function MyController() {
// ...
}
```
After:
```javascript
angular.module('myApp', []).controller('MyController', [function() {
// ...
}]);
```
Although it's not recommended, you can re-enable the old behavior like this:
```javascript
angular.module('myModule').config(['$controllerProvider', function($controllerProvider) {
// this option might be handy for migrating old apps, but please don't use it
// in new ones!
$controllerProvider.allowGlobals();
}]);
```
## Angular Expression Parsing (`$parse` + `$interpolate`)
- due to [77ada4c8](https://github.com/angular/angular.js/commit/77ada4c82d6b8fc6d977c26f3cdb48c2f5fbe5a5),
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
This is to disallow changing the behaviour of existing functions
in an unforseen fashion.
in an unforeseen fashion.
- due to [6081f207](https://github.com/angular/angular.js/commit/6081f20769e64a800ee8075c168412b21f026d99),
@@ -877,7 +915,7 @@ of `$sce.trustAsHtml(string)`. When bound to a plain string, the string is sanit
module is not loaded) and the bound expression evaluates to a value that is not trusted an
exception is thrown.
When using this directive you can either include `ngSanitize` in your module's dependencis (See the
When using this directive you can either include `ngSanitize` in your module's dependencies (See the
example at the {@link ngBindHtml} reference) or use the {@link $sce} service to set the value as
trusted.
@@ -1134,10 +1172,10 @@ freely available to JavaScript code (as before).
Angular expressions execute in a limited context. They do not have
direct access to the global scope, `window`, `document` or the Function
constructor. However, they have direct access to names/properties on
the scope chain. It has been a long standing best practice to keep
constructor. However, they have direct access to names/properties on
the scope chain. It has been a long standing best practice to keep
sensitive APIs outside of the scope chain (in a closure or your
controller.) That's easier said that done for two reasons:
controller.) That's easier said than done for two reasons:
1. JavaScript does not have a notion of private properties so if you need
someone on the scope chain for JavaScript use, you also expose it to
+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 scopes to DOM nodes, and adds CSS classes
to data-bound elements. The information that is not included is:
By default AngularJS attaches information about binding and scopes to DOM nodes,
and adds CSS classes to data-bound elements:
As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-class` is attached to the corresponding element.
- As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
`ng-binding` are attached to the corresponding element.
Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
- Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
CSS class are attached to the corresponding element. These scope references can then be accessed via
`element.scope()` and `element.isolateScope()`.
+1 -1
View File
@@ -181,7 +181,7 @@ of a real browser alert.
```js
var mock, notify;
beforeEach(module('myServiceModule'));
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
+2 -2
View File
@@ -15,14 +15,14 @@ development.
production.
To point your code to an angular script on the Google CDN server, use the following template. This
example points to the minified version 1.2.0:
example points to the minified version 1.3.12:
```
<!doctype html>
<html ng-app>
<head>
<title>My Angular App</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.12/angular.min.js"></script>
</head>
<body>
</body>
+7 -7
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,13 +61,13 @@ 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?
The size of the file is < 36KB compressed and minified.
The size of the file is ~50KB compressed and minified.
### Can I use the open-source Closure Library with Angular?
@@ -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.
+3
View File
@@ -33,6 +33,9 @@ To see the app running in a browser, open a *separate* terminal/command line tab
run `npm start` to start the web server. Now, open a browser window for the app and navigate to
<a href="http://localhost:8000/app/" target="_blank">`http://localhost:8000/app/`</a>
Note that if you already ran the master branch app prior to checking out step-0, you may see the cached
master version of the app in your browser window at this point. Just hit refresh to re-load the page.
You can now see the page in your browser. It's not very exciting, but that's OK.
The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
+7 -7
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>
@@ -21,7 +21,7 @@ multiple views by adding routing, using an Angular module called 'ngRoute'.
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
is distributed separately from the core Angular framework.
We are using [Bower][bower] to install client side dependencies. This step updates the
We are using [Bower][bower] to install client-side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
```json
@@ -46,7 +46,7 @@ The new dependency `"angular-route": "~1.3.0"` tells bower to install a version
angular-route component that is compatible with version 1.3.x. We must tell bower to download
and install this dependency.
If you have bower installed globally then you can run `bower install` but for this project we have
If you have bower installed globally, then you can run `bower install` but for this project, we have
preconfigured npm to run bower install for us:
```
@@ -70,7 +70,7 @@ the current "route" — the view that is currently displayed to the user.
Application routes in Angular are declared via the {@link ngRoute.$routeProvider $routeProvider},
which is the provider of the {@link ngRoute.$route $route service}. This service makes it easy to
wire together controllers, view templates, and the current URL location in the browser. Using this
feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
feature, we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
utilize the browser's history (back and forward navigation) and bookmarks.
@@ -81,7 +81,7 @@ AngularJS, so it's important for you to understand a thing or two about how it w
When the application bootstraps, Angular creates an injector that will be used to find and inject all
of the services that are required by your app. The injector itself doesn't know anything about what
`$http` or `$route` services do, in fact it doesn't even know about the existence of these services
`$http` or `$route` services do. In fact, the injector doesn't even know about the existence of these services
unless it is configured with proper module definitions.
The injector only carries out the following steps :
@@ -295,7 +295,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
Again, note that we created a new module called `phonecatControllers`. For small AngularJS
applications, it's common to create just one module for all of your controllers if there are just a
few. As your application grows it is quite common to refactor your code into additional modules.
few. As your application grows, it is quite common to refactor your code into additional modules.
For larger apps, you will probably want to create separate modules for each major feature of
your app.
@@ -349,7 +349,7 @@ the same binding into the `phone-list.html` template, the binding will work as e
<div style="display: none">
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
`PhoneListCtrl`, let's shadow it with `this.hero = 'Batman'`. In `PhoneDetailCtrl`, we'll use
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
inheritance and model property shadowing do some wonders.
+1 -1
View File
@@ -31,7 +31,7 @@ phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$h
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
};
}]);
```
+1 -1
View File
@@ -288,5 +288,5 @@ learn how to improve this application with animations.
<ul doc-tutorial-nav="11"></ul>
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
[jasmine-matchers]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
[bower]: http://bower.io/
+3 -3
View File
@@ -97,7 +97,7 @@ __`app/index.html`.__
...
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/jquery/dist/jquery.js"></script>
...
@@ -225,7 +225,7 @@ inserted into and removed from the list:
* The `ng-leave` class is applied when they're removed from the list.
The phone listing items are added and removed depending on the data passed to the `ng-repeat` attribute.
For example, if the filter data changes the items will be animated in and out of the repeat list.
For example, if the filter data changes, the items will be animated in and out of the repeat list.
Something important to note is that when an animation occurs, two sets of CSS classes
are added to the element:
@@ -233,7 +233,7 @@ are added to the element:
1. a "starting" class that represents the style at the beginning of the animation
2. an "active" class that represents the style at the end of the animation
The name of the starting class is the name of event that is fired (like `enter`, `move` or `leave`) prefixed with
The name of the starting class is the name of the event that is fired (like `enter`, `move` or `leave`) prefixed with
`ng-`. So an `enter` event will result in a class called `ng-enter`.
The active class name is the same as the starting class's but with an `-active` suffix.
+7 -11
View File
@@ -2912,16 +2912,12 @@ goog.i18n.DateTimeSymbols_my = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', '', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -2945,7 +2941,7 @@ goog.i18n.DateTimeSymbols_my = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
FIRSTDAYOFWEEK: 6,
+7 -11
View File
@@ -14493,16 +14493,12 @@ goog.i18n.DateTimeSymbols_my_MM = {
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်နဝါရီ', 'ဖေဖော်ဝါရီ',
'မတ်', 'ဧပြီ', 'မေ', 'ဇွန်',
'ဇူလိုင်', 'ဩဂုတ်', 'စက်တင်ဘာ',
'အောက်တိုဘာ', 'နိုဝင်ဘာ',
'ဒီဇင်ဘာ'],
STANDALONESHORTMONTHS: ['ဇန်နဝါရီ',
'ဖေဖော်ဝါရီ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူလိုင်', 'ဩဂုတ်',
'စက်တင်ဘာ', 'အောက်တိုဘာ',
'နိုဝင်ဘာ', 'ဒီဇင်ဘာ'],
SHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ', 'မေ',
'ဇွန်', 'ဇူ', '', 'စက်', 'အောက်',
'နို', 'ဒီ'],
STANDALONESHORTMONTHS: ['ဇန်', 'ဖေ', 'မတ်', 'ဧပြီ',
'မေ', 'ဇွန်', 'ဇူ', 'ဩ', 'စက်', 'အောက်',
'နို', 'ဒီ'],
WEEKDAYS: ['တနင်္ဂနွေ', 'တနင်္လာ',
'အင်္ဂါ', 'ဗုဒ္ဓဟူး',
'ကြာသပတေး', 'သောကြာ', 'စနေ'],
@@ -14526,7 +14522,7 @@ goog.i18n.DateTimeSymbols_my_MM = {
'တတိယ သုံးလပတ်',
'စတုတ္ထ သုံးလပတ်'],
AMPMS: ['နံနက်', 'ညနေ'],
DATEFORMATS: ['EEEE, y MMMM dd', 'y MMMM d', 'y MMM d', 'yy/MM/dd'],
DATEFORMATS: ['EEEE, dd MMMM y', 'd MMMM y', 'd MMM y', 'dd-MM-yy'],
TIMEFORMATS: ['HH:mm:ss zzzz', 'HH:mm:ss z', 'HH:mm:ss', 'HH:mm'],
DATETIMEFORMATS: ['{1}မှာ {0}', '{1} {0}', '{1} {0}', '{1} {0}'],
FIRSTDAYOFWEEK: 6,
+1 -1
View File
@@ -2112,7 +2112,7 @@ goog.i18n.NumberFormatSymbols_lt = {
SCIENTIFIC_PATTERN: '#E0',
PERCENT_PATTERN: '#,##0\u00A0%',
CURRENCY_PATTERN: '#,##0.00\u00A0\u00A4',
DEF_CURRENCY_CODE: 'LTL'
DEF_CURRENCY_CODE: 'EUR'
};
+6 -6
View File
@@ -35,18 +35,18 @@ module.exports = function(config, specificOptions) {
'SL_Chrome': {
base: 'SauceLabs',
browserName: 'chrome',
version: '34'
version: '39'
},
'SL_Firefox': {
base: 'SauceLabs',
browserName: 'firefox',
version: '26'
version: '31'
},
'SL_Safari': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
platform: 'OS X 10.10',
version: '8'
},
'SL_IE_9': {
base: 'SauceLabs',
@@ -71,13 +71,13 @@ module.exports = function(config, specificOptions) {
base: 'BrowserStack',
browser: 'chrome',
os: 'OS X',
os_version: 'Mountain Lion'
os_version: 'Yosemite'
},
'BS_Safari': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mountain Lion'
os_version: 'Yosemite'
},
'BS_Firefox': {
base: 'BrowserStack',
-7
View File
@@ -14,13 +14,6 @@ var CSP_CSS_HEADER = '/* Include this file in your html if you are using the CSP
module.exports = {
init: function() {
if (!process.env.TRAVIS) {
shell.exec('npm install');
}
},
startKarma: function(config, singleRun, done){
var browsers = grunt.option('browsers');
var reporters = grunt.option('reporters');
+2 -2
View File
@@ -12,9 +12,9 @@ set -e
# before_script:
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3-linux.tar.gz"
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3.6-linux.tar.gz"
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
CONNECT_DOWNLOAD="sc-4.3-linux.tar.gz"
CONNECT_DOWNLOAD="sc-4.3.6-linux.tar.gz"
CONNECT_LOG="$LOGS_DIR/sauce-connect"
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
+14 -14
View File
@@ -16,9 +16,9 @@ var currentPackage, previousVersions, cdnVersion, gitRepoInfo;
var getPackage = function() {
// Search up the folder hierarchy for the first package.json
var packageFolder = path.resolve('.');
while ( !fs.existsSync(path.join(packageFolder, 'package.json')) ) {
while (!fs.existsSync(path.join(packageFolder, 'package.json'))) {
var parent = path.dirname(packageFolder);
if ( parent === packageFolder) { break; }
if (parent === packageFolder) { break; }
packageFolder = parent;
}
return JSON.parse(fs.readFileSync(path.join(packageFolder,'package.json'), 'UTF-8'));
@@ -48,11 +48,11 @@ var getGitRepoInfo = function() {
* @return {String} The codename if found, otherwise null/undefined
*/
var getCodeName = function(tagName) {
var gitCatOutput = shell.exec('git cat-file -p '+ tagName, {silent:true}).output;
var gitCatOutput = shell.exec('git cat-file -p ' + tagName, {silent:true}).output;
var tagMessage = gitCatOutput.match(/^.*codename.*$/mg)[0];
var codeName = tagMessage && tagMessage.match(/codename\((.*)\)/)[1];
if (!codeName) {
throw new Error("Could not extract release code name. The message of tag "+tagName+
throw new Error("Could not extract release code name. The message of tag " + tagName +
" must match '*codename(some release name)*'");
}
return codeName;
@@ -65,7 +65,7 @@ var getCodeName = function(tagName) {
*/
function getBuild() {
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
return 'sha.'+hash;
return 'sha.' + hash;
}
@@ -76,14 +76,14 @@ function getBuild() {
var getTaggedVersion = function() {
var gitTagResult = shell.exec('git describe --exact-match', {silent:true});
if ( gitTagResult.code === 0 ) {
if (gitTagResult.code === 0) {
var tag = gitTagResult.output.trim();
var version = semver.parse(tag);
if ( version && semver.satisfies(version, currentPackage.branchVersion)) {
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;
}
}
@@ -102,7 +102,7 @@ var getPreviousVersions = function() {
var repo_url = currentPackage.repository.url;
var tagResults = shell.exec('git ls-remote --tags ' + repo_url,
{silent: true});
if ( tagResults.code === 0 ) {
if (tagResults.code === 0) {
return _(tagResults.output.match(/v[0-9].*[0-9]$/mg))
.map(function(tag) {
var version = semver.parse(tag);
@@ -112,7 +112,7 @@ var getPreviousVersions = function() {
.map(function(version) {
version.docsUrl = 'http://code.angularjs.org/' + version.version + '/docs';
// Versions before 1.0.2 had a different docs folder name
if ( version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2 ) ) {
if (version.major < 1 || (version.major === 1 && version.minor === 0 && version.dot < 2)) {
version.docsUrl += '-' + version.version;
}
return version;
@@ -134,10 +134,10 @@ var getCdnVersion = function() {
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 '+
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});
if ( cdnResult.code === 0 ) {
if (cdnResult.code === 0) {
var statusCode = cdnResult.output.trim();
if (statusCode === '200') {
cdnVersion = version;
@@ -159,9 +159,9 @@ var getSnapshotVersion = function() {
})
.last();
if ( !version ) {
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
File diff suppressed because it is too large Load Diff
+6103 -1501
View File
File diff suppressed because it is too large Load Diff
+12 -6
View File
@@ -1,16 +1,23 @@
{
"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"
},
"engines": {
"node": "~0.10",
"npm": "~2.5"
},
"engineStrict": true,
"devDependencies": {
"angular-benchpress": "0.x.x",
"benchmark": "1.x.x",
"bower": "~1.3.9",
"browserstacktunnel-wrapper": "~1.3.1",
"canonical-path": "0.0.2",
"cheerio": "^0.17.0",
"dgeni": "^0.4.0",
"dgeni-packages": "^0.10.0",
"event-stream": "~3.1.0",
@@ -23,7 +30,7 @@
"grunt-contrib-jshint": "~0.10.0",
"grunt-ddescribe-iit": "~0.0.1",
"grunt-jasmine-node": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"grunt-jscs": "~0.7.1",
"grunt-jscs": "~1.2.0",
"grunt-merge-conflict": "~0.0.1",
"grunt-shell": "~1.1.1",
"gulp": "~3.8.0",
@@ -37,7 +44,7 @@
"jasmine-node": "~1.14.5",
"jasmine-reporters": "~1.0.1",
"jshint-stylish": "~1.0.0",
"karma": "vojtajina/karma#socketio_10",
"karma": "~0.12.0",
"karma-browserstack-launcher": "0.1.2",
"karma-chrome-launcher": "0.1.5",
"karma-firefox-launcher": "0.1.3",
@@ -51,7 +58,7 @@
"marked": "~0.3.0",
"node-html-encoder": "0.0.2",
"promises-aplus-tests": "~2.1.0",
"protractor": "1.4.0",
"protractor": "^1.6.0",
"q": "~1.0.0",
"q-io": "^1.10.9",
"qq": "^0.3.5",
@@ -59,8 +66,7 @@
"semver": "~4.0.3",
"shelljs": "~0.3.0",
"sorted-object": "^1.0.0",
"stringmap": "^0.2.2",
"cheerio": "^0.17.0"
"stringmap": "^0.2.2"
},
"licenses": [
{
+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
+15
View File
@@ -63,6 +63,21 @@ function prepare {
cp $BUILD_DIR/angular-csp.css $TMP_DIR/bower-angular
#
# Run local precommit script if there is one
#
for repo in "${REPOS[@]}"
do
if [ -f $TMP_DIR/bower-$repo/precommit.sh ]
then
echo "-- Running precommit.sh script for bower-$repo"
cd $TMP_DIR/bower-$repo
$TMP_DIR/bower-$repo/precommit.sh
cd $SCRIPT_DIR
fi
done
#
# update bower.json
# tag each repo
@@ -10,22 +10,17 @@
var _ = require('lodash');
var sorted = require('sorted-object');
var fs = require('fs');
var path = require('path');
function cleanModule(module, name) {
// keep `from` and `resolve` properties for git dependencies, delete otherwise
if (!(module.resolved && module.resolved.match(/^git:\/\//))) {
delete module.from;
// keep `resolve` properties for git dependencies, delete otherwise
delete module.from;
if (!(module.resolved && module.resolved.match(/^git(\+[a-z]+)?:\/\//))) {
delete module.resolved;
}
if (name === 'chokidar') {
if (module.version === '0.8.1') {
delete module.dependencies;
}
}
_.forEach(module.dependencies, function(mod, name) {
cleanModule(mod, name);
});
@@ -33,10 +28,11 @@ function cleanModule(module, name) {
console.log('Reading npm-shrinkwrap.json');
var shrinkwrap = require('./../npm-shrinkwrap.json');
var shrinkwrap = require('../../npm-shrinkwrap.json');
console.log('Cleaning shrinkwrap object');
cleanModule(shrinkwrap, shrinkwrap.name);
console.log('Writing cleaned npm-shrinkwrap.json');
fs.writeFileSync('./npm-shrinkwrap.json', JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
var cleanShrinkwrapPath = path.join(__dirname, '..', '..', 'npm-shrinkwrap.clean.json');
console.log('Writing cleaned to', cleanShrinkwrapPath);
fs.writeFileSync(cleanShrinkwrapPath, JSON.stringify(sorted(shrinkwrap), null, 2) + "\n");
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash
set -e
SHRINKWRAP_FILE=npm-shrinkwrap.json
SHRINKWRAP_CACHED_FILE=node_modules/npm-shrinkwrap.cached.json
if diff -q $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE; then
echo 'No shrinkwrap changes detected. npm install will be skipped...';
else
echo 'Blowing away node_modules and reinstalling npm dependencies...'
rm -rf node_modules
npm install
cp $SHRINKWRAP_FILE $SHRINKWRAP_CACHED_FILE
echo 'npm install successful!'
fi
+1
View File
@@ -16,6 +16,7 @@ if [ $JOB = "unit" ]; then
grunt test:unit --browsers $BROWSERS --reporters dots
grunt ci-checks
grunt tests:docs --browsers $BROWSERS --reporters dots
elif [ $JOB = "docs-e2e" ]; then
grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js"
elif [ $JOB = "e2e" ]; then
if [ $TEST_TARGET = "jquery" ]; then
+4 -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,
@@ -153,6 +152,9 @@
"urlResolve": false,
"urlIsSameOrigin": false,
/* ng/controller.js */
"identifierForController": false,
/* ng/compile.js */
"directiveNormalize": false,
+89 -17
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,
@@ -59,6 +58,7 @@
shallowCopy: true,
equals: true,
csp: true,
jq: true,
concat: true,
sliceArgs: true,
bind: true,
@@ -272,12 +272,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,8 +313,7 @@ function nextUid() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
}
else {
} else {
delete obj.$$hashKey;
}
}
@@ -357,7 +352,7 @@ function extend(dst) {
return dst;
}
function int(str) {
function toInt(str) {
return parseInt(str, 10);
}
@@ -589,6 +584,12 @@ function isPromiseLike(obj) {
}
var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/;
function isTypedArray(value) {
return TYPED_ARRAY_REGEXP.test(toString.call(value));
}
var trim = function(value) {
return isString(value) ? value.trim() : value;
};
@@ -626,8 +627,9 @@ function isElement(node) {
*/
function makeMap(str) {
var obj = {}, items = str.split(","), i;
for (i = 0; i < items.length; i++)
obj[ items[i] ] = true;
for (i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
@@ -642,9 +644,10 @@ function includes(array, obj) {
function arrayRemove(array, value) {
var index = array.indexOf(value);
if (index >= 0)
if (index >= 0) {
array.splice(index, 1);
return value;
}
return index;
}
/**
@@ -710,12 +713,18 @@ function copy(source, destination, stackSource, stackDest) {
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
}
if (isTypedArray(destination)) {
throw ngMinErr('cpta',
"Can't copy! TypedArray destination cannot be mutated.");
}
if (!destination) {
destination = source;
if (source) {
if (isArray(source)) {
destination = copy(source, [], stackSource, stackDest);
} else if (isTypedArray(source)) {
destination = new source.constructor(source);
} else if (isDate(source)) {
destination = new Date(source.getTime());
} else if (isRegExp(source)) {
@@ -893,7 +902,61 @@ var csp = function() {
return (csp.isActive_ = active);
};
/**
* @ngdoc directive
* @module ng
* @name ngJq
*
* @element ANY
* @param {string=} the name of the library available under `window`
* to be used for angular.element
* @description
* Use this directive to force the angular.element library. This should be
* used to force either jqLite by leaving ng-jq blank or setting the name of
* the jquery variable under window (eg. jQuery).
*
* Since this directive is global for the angular library, it is recommended
* that it's added to the same element as ng-app or the HTML element, but it is not mandatory.
* It needs to be noted that only the first instance of `ng-jq` will be used and all others
* ignored.
*
* @example
* This example shows how to force jqLite using the `ngJq` directive to the `html` tag.
```html
<!doctype html>
<html ng-app ng-jq>
...
...
</html>
```
* @example
* This example shows how to use a jQuery based library of a different name.
* The library name must be available at the top most 'window'.
```html
<!doctype html>
<html ng-app ng-jq="jQueryLib">
...
...
</html>
```
*/
var jq = function() {
if (isDefined(jq.name_)) return jq.name_;
var el;
var i, ii = ngAttrPrefixes.length;
for (i = 0; i < ii; ++i) {
if (el = document.querySelector('[' + ngAttrPrefixes[i].replace(':', '\\:') + 'jq]')) {
break;
}
}
var name;
if (el) {
name = getNgAttribute(el, "jq");
}
return (jq.name_ = name);
};
function concat(array1, array2, index) {
return array1.concat(slice.call(array2, index));
@@ -1338,7 +1401,7 @@ function angularInit(element, bootstrap) {
* @param {DOMElement} element DOM element which is the root of angular application.
* @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
* Each item in the array should be the name of a predefined module or a (DI annotated)
* function that will be invoked by the injector as a run block.
* function that will be invoked by the injector as a `config` block.
* See: {@link angular.module modules}
* @param {Object=} config an object for defining configuration options for the application. The
* following keys are supported:
@@ -1408,8 +1471,12 @@ function bootstrap(element, modules, config) {
forEach(extraModules, function(module) {
modules.push(module);
});
doBootstrap();
return doBootstrap();
};
if (isFunction(angular.resumeDeferredBootstrap)) {
angular.resumeDeferredBootstrap();
}
}
/**
@@ -1462,7 +1529,12 @@ function bindJQuery() {
}
// bind to jQuery if present;
jQuery = window.jQuery;
var jqName = jq();
jQuery = window.jQuery; // use default jQuery.
if (isDefined(jqName)) { // `ngJq` present
jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`.
}
// Use jQuery if it exists with proper functionality, otherwise default to us.
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
// Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
+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) {
+3 -3
View File
@@ -800,7 +800,7 @@ function createInjector(modulesToLoad, strictDi) {
}
var args = [],
$inject = annotate(fn, strictDi, serviceName),
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
@@ -829,7 +829,7 @@ function createInjector(modulesToLoad, strictDi) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
@@ -839,7 +839,7 @@ function createInjector(modulesToLoad, strictDi) {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
+2 -1
View File
@@ -851,8 +851,9 @@ forEach({
children: function(element) {
var children = [];
forEach(element.childNodes, function(element) {
if (element.nodeType === NODE_TYPE_ELEMENT)
if (element.nodeType === NODE_TYPE_ELEMENT) {
children.push(element);
}
});
return children;
},
+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;
+254 -161
View File
@@ -64,7 +64,7 @@
* templateNamespace: 'html',
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
* controllerAs: 'stringAlias',
* controllerAs: 'stringIdentifier',
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile: function compile(tElement, tAttrs, transclude) {
* return {
@@ -224,9 +224,10 @@
*
*
* #### `controllerAs`
* Controller alias at the directive scope. An alias for the controller so it
* can be referenced at the directive template. The directive needs to define a scope for this
* configuration to be used. Useful in the case when directive is used as component.
* Identifier name for a reference to the controller in the directive's scope.
* This allows the controller to be referenced from the directive template. The directive
* needs to define a scope for this configuration to be used. Useful in the case when
* directive is used as component.
*
*
* #### `restrict`
@@ -477,7 +478,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>
*
@@ -712,7 +713,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// 'on' and be composed of only English letters.
var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/;
function parseIsolateBindings(scope, directiveName) {
function parseIsolateBindings(scope, directiveName, isController) {
var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/;
var bindings = {};
@@ -722,9 +723,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (!match) {
throw $compileMinErr('iscp',
"Invalid isolate scope definition for directive '{0}'." +
"Invalid {3} for directive '{0}'." +
" Definition: {... {1}: '{2}' ...}",
directiveName, scopeName, definition);
directiveName, scopeName, definition,
(isController ? "controller bindings definition" :
"isolate scope definition"));
}
bindings[scopeName] = {
@@ -738,6 +741,43 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return bindings;
}
function parseDirectiveBindings(directive, directiveName) {
var bindings = {
isolateScope: null,
bindToController: null
};
if (isObject(directive.scope)) {
if (directive.bindToController === true) {
bindings.bindToController = parseIsolateBindings(directive.scope,
directiveName, true);
bindings.isolateScope = {};
} else {
bindings.isolateScope = parseIsolateBindings(directive.scope,
directiveName, false);
}
}
if (isObject(directive.bindToController)) {
bindings.bindToController =
parseIsolateBindings(directive.bindToController, directiveName, true);
}
if (isObject(bindings.bindToController)) {
var controller = directive.controller;
var controllerAs = directive.controllerAs;
if (!controller) {
// There is no controller, there may or may not be a controllerAs property
throw $compileMinErr('noctrl',
"Cannot bind to controller without directive '{0}'s controller.",
directiveName);
} else if (!identifierForController(controller, controllerAs)) {
// There is a controller, but no identifier or controllerAs property
throw $compileMinErr('noident',
"Cannot bind to controller without identifier for directive '{0}'.",
directiveName);
}
}
return bindings;
}
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -775,8 +815,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directive.name = directive.name || name;
directive.require = directive.require || (directive.controller && directive.name);
directive.restrict = directive.restrict || 'EA';
if (isObject(directive.scope)) {
directive.$$isolateBindings = parseIsolateBindings(directive.scope, directive.name);
var bindings = directive.$$bindings =
parseDirectiveBindings(directive, directive.name);
if (isObject(bindings.isolateScope)) {
directive.$$isolateBindings = bindings.isolateScope;
}
directives.push(directive);
} catch (e) {
@@ -1338,6 +1380,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (nodeLinkFn.scope) {
childScope = scope.$new();
compile.$$addScopeInfo(jqLite(node), childScope);
var destroyBindings = nodeLinkFn.$$destroyBindings;
if (destroyBindings) {
nodeLinkFn.$$destroyBindings = null;
childScope.$on('$destroyed', destroyBindings);
}
} else {
childScope = scope;
}
@@ -1357,7 +1404,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
childBoundTranscludeFn = null;
}
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn,
nodeLinkFn);
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
@@ -1450,6 +1498,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// use class as directive
className = node.className;
if (isObject(className)) {
// Maybe SVGAnimatedString
className = className.animVal;
}
if (isString(className) && className !== '') {
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
nName = directiveNormalize(match[2]);
@@ -1562,7 +1614,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var terminalPriority = -Number.MAX_VALUE,
newScopeDirective,
controllerDirectives = previousCompileContext.controllerDirectives,
controllers,
newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
templateDirective = previousCompileContext.templateDirective,
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
@@ -1620,7 +1671,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (!directive.templateUrl && directive.controller) {
directiveValue = directive.controller;
controllerDirectives = controllerDirectives || {};
controllerDirectives = controllerDirectives || createMap();
assertNoDuplicate("'" + directiveName + "' controller",
controllerDirectives[directiveName], directive, $compileNode);
controllerDirectives[directiveName] = directive;
@@ -1788,53 +1839,77 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
function getControllers(directiveName, require, $element, elementControllers) {
var value, retrievalMethod = 'data', optional = false;
var $searchElement = $element;
var match;
var value;
if (isString(require)) {
match = require.match(REQUIRE_PREFIX_REGEXP);
require = require.substring(match[0].length);
var match = require.match(REQUIRE_PREFIX_REGEXP);
var name = require.substring(match[0].length);
var inheritType = match[1] || match[3];
var optional = match[2] === '?';
if (match[3]) {
if (match[1]) match[3] = null;
else match[1] = match[3];
}
if (match[1] === '^') {
retrievalMethod = 'inheritedData';
} else if (match[1] === '^^') {
retrievalMethod = 'inheritedData';
$searchElement = $element.parent();
}
if (match[2] === '?') {
optional = true;
//If only parents then start at the parent element
if (inheritType === '^^') {
$element = $element.parent();
//Otherwise attempt getting the controller from elementControllers in case
//the element is transcluded (and has no data) and to avoid .data if possible
} else {
value = elementControllers && elementControllers[name];
value = value && value.instance;
}
value = null;
if (elementControllers && retrievalMethod === 'data') {
if (value = elementControllers[require]) {
value = value.instance;
}
if (!value) {
var dataName = '$' + name + 'Controller';
value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName);
}
value = value || $searchElement[retrievalMethod]('$' + require + 'Controller');
if (!value && !optional) {
throw $compileMinErr('ctreq',
"Controller '{0}', required by directive '{1}', can't be found!",
require, directiveName);
name, directiveName);
}
return value || null;
} else if (isArray(require)) {
value = [];
forEach(require, function(require) {
value.push(getControllers(directiveName, require, $element, elementControllers));
});
for (var i = 0, ii = require.length; i < ii; i++) {
value[i] = getControllers(directiveName, require[i], $element, elementControllers);
}
}
return value;
return value || null;
}
function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) {
var elementControllers = createMap();
for (var controllerKey in controllerDirectives) {
var directive = controllerDirectives[controllerKey];
var locals = {
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
$element: $element,
$attrs: attrs,
$transclude: transcludeFn
};
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
var controller = directive.controller;
if (controller == '@') {
controller = attrs[directive.name];
}
var controllerInstance = $controller(controller, locals, true, directive.controllerAs);
// For directives with element transclusion the element is a comment,
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
// clean up (http://bugs.jquery.com/ticket/8335).
// Instead, we save the controllers for the element in a local hash and attach to .data
// later, once we have the actual element.
elementControllers[directive.name] = controllerInstance;
if (!hasElementTranscludeDirective) {
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
}
}
return elementControllers;
}
function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn,
thisLinkFn) {
var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element,
attrs;
@@ -1858,126 +1933,48 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
if (controllerDirectives) {
// TODO: merge `controllers` and `elementControllers` into single object.
controllers = {};
elementControllers = {};
forEach(controllerDirectives, function(directive) {
var locals = {
$scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope,
$element: $element,
$attrs: attrs,
$transclude: transcludeFn
}, controllerInstance;
controller = directive.controller;
if (controller == '@') {
controller = attrs[directive.name];
}
controllerInstance = $controller(controller, locals, true, directive.controllerAs);
// For directives with element transclusion the element is a comment,
// but jQuery .data doesn't support attaching data to comment nodes as it's hard to
// clean up (http://bugs.jquery.com/ticket/8335).
// Instead, we save the controllers for the element in a local hash and attach to .data
// later, once we have the actual element.
elementControllers[directive.name] = controllerInstance;
if (!hasElementTranscludeDirective) {
$element.data('$' + directive.name + 'Controller', controllerInstance.instance);
}
controllers[directive.name] = controllerInstance;
});
elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope);
}
if (newIsolateScopeDirective) {
// Initialize isolate scope bindings for new isolate scope directive.
compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
templateDirective === newIsolateScopeDirective.$$originalDirective)));
compile.$$addScopeClass($element, true);
var isolateScopeController = controllers && controllers[newIsolateScopeDirective.name];
var isolateBindingContext = isolateScope;
if (isolateScopeController && isolateScopeController.identifier &&
newIsolateScopeDirective.bindToController === true) {
isolateBindingContext = isolateScopeController.instance;
}
forEach(isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings, function(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
mode = definition.mode, // @, =, or &
lastValue,
parentGet, parentSet, compare;
switch (mode) {
case '@':
attrs.$observe(attrName, function(value) {
isolateBindingContext[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = scope;
if (attrs[attrName]) {
// If the attribute has been provided then we trigger an interpolation to ensure
// the value is there for use in the link fn
isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope);
}
break;
case '=':
if (optional && !attrs[attrName]) {
return;
}
parentGet = $parse(attrs[attrName]);
if (parentGet.literal) {
compare = equals;
} else {
compare = function(a, b) { return a === b || (a !== a && b !== b); };
}
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
lastValue = isolateBindingContext[scopeName] = parentGet(scope);
throw $compileMinErr('nonassign',
"Expression '{0}' used with directive '{1}' is non-assignable!",
attrs[attrName], newIsolateScopeDirective.name);
};
lastValue = isolateBindingContext[scopeName] = parentGet(scope);
var parentValueWatch = function parentValueWatch(parentValue) {
if (!compare(parentValue, isolateBindingContext[scopeName])) {
// we are out of sync and need to copy
if (!compare(parentValue, lastValue)) {
// parent changed and it has precedence
isolateBindingContext[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = isolateBindingContext[scopeName]);
}
}
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
var unwatch;
if (definition.collection) {
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
}
isolateScope.$on('$destroy', unwatch);
break;
case '&':
parentGet = $parse(attrs[attrName]);
isolateBindingContext[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
}
});
isolateScope.$$isolateBindings =
newIsolateScopeDirective.$$isolateBindings;
initializeDirectiveBindings(scope, attrs, isolateScope,
isolateScope.$$isolateBindings,
newIsolateScopeDirective, isolateScope);
}
if (controllers) {
forEach(controllers, function(controller) {
controller();
});
controllers = null;
if (elementControllers) {
// Initialize bindToController bindings for new/isolate scopes
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
var bindings;
var controllerForBindings;
if (scopeDirective && elementControllers[scopeDirective.name]) {
bindings = scopeDirective.$$bindings.bindToController;
controller = elementControllers[scopeDirective.name];
if (controller && controller.identifier && bindings) {
controllerForBindings = controller;
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, controller.instance,
bindings, scopeDirective);
}
}
for (i in elementControllers) {
controller = elementControllers[i];
var controllerResult = controller();
if (controllerResult !== controller.instance &&
controller === controllerForBindings) {
// Remove and re-install bindToController bindings
thisLinkFn.$$destroyBindings();
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, controllerResult,
bindings, scopeDirective);
}
}
}
// PRELINKING
@@ -2151,8 +2148,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
afterTemplateChildLinkFn,
beforeTemplateCompileNode = $compileNode[0],
origAsyncDirective = directives.shift(),
// The fact that we have to copy and patch the directive seems wrong!
derivedSyncDirective = extend({}, origAsyncDirective, {
derivedSyncDirective = inherit(origAsyncDirective, {
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
@@ -2236,7 +2232,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
childBoundTranscludeFn = boundTranscludeFn;
}
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement,
childBoundTranscludeFn);
childBoundTranscludeFn, afterTemplateNodeLinkFn);
}
linkQueue = null;
});
@@ -2253,7 +2249,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
}
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn,
afterTemplateNodeLinkFn);
}
};
}
@@ -2500,6 +2497,102 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
$exceptionHandler(e, startingTag($element));
}
}
// Set up $watches for isolate scope and controller bindings. This process
// only occurs for isolate scopes and new scopes with controllerAs.
function initializeDirectiveBindings(scope, attrs, destination, bindings,
directive, newScope) {
var onNewScopeDestroyed;
forEach(bindings, function(definition, scopeName) {
var attrName = definition.attrName,
optional = definition.optional,
mode = definition.mode, // @, =, or &
lastValue,
parentGet, parentSet, compare;
switch (mode) {
case '@':
attrs.$observe(attrName, function(value) {
destination[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = scope;
if (attrs[attrName]) {
// If the attribute has been provided then we trigger an interpolation to ensure
// the value is there for use in the link fn
destination[scopeName] = $interpolate(attrs[attrName])(scope);
}
break;
case '=':
if (optional && !attrs[attrName]) {
return;
}
parentGet = $parse(attrs[attrName]);
if (parentGet.literal) {
compare = equals;
} else {
compare = function(a, b) { return a === b || (a !== a && b !== b); };
}
parentSet = parentGet.assign || function() {
// reset the change, or we will throw this exception on every $digest
lastValue = destination[scopeName] = parentGet(scope);
throw $compileMinErr('nonassign',
"Expression '{0}' used with directive '{1}' is non-assignable!",
attrs[attrName], directive.name);
};
lastValue = destination[scopeName] = parentGet(scope);
var parentValueWatch = function parentValueWatch(parentValue) {
if (!compare(parentValue, destination[scopeName])) {
// we are out of sync and need to copy
if (!compare(parentValue, lastValue)) {
// parent changed and it has precedence
destination[scopeName] = parentValue;
} else {
// if the parent can be assigned then do so
parentSet(scope, parentValue = destination[scopeName]);
}
}
return lastValue = parentValue;
};
parentValueWatch.$stateful = true;
var unwatch;
if (definition.collection) {
unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
} else {
unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
}
onNewScopeDestroyed = (onNewScopeDestroyed || []);
onNewScopeDestroyed.push(unwatch);
break;
case '&':
// Don't assign Object.prototype method to scope
if (!attrs.hasOwnProperty(attrName) && optional) break;
parentGet = $parse(attrs[attrName]);
// Don't assign noop to destination if expression is not valid
if (parentGet === noop && optional) break;
destination[scopeName] = function(locals) {
return parentGet(scope, locals);
};
break;
}
});
var destroyBindings = onNewScopeDestroyed ? function destroyBindings() {
for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) {
onNewScopeDestroyed[i]();
}
} : noop;
if (newScope && destroyBindings !== noop) {
newScope.$on('$destroy', destroyBindings);
return noop;
}
return destroyBindings;
}
}];
}
+31 -7
View File
@@ -1,5 +1,18 @@
'use strict';
var $controllerMinErr = minErr('$controller');
var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
function identifierForController(controller, ident) {
if (ident && isString(ident)) return ident;
if (isString(controller)) {
var match = CNTRL_REG.exec(controller);
if (match) return match[3];
}
}
/**
* @ngdoc provider
* @name $controllerProvider
@@ -12,9 +25,7 @@
*/
function $ControllerProvider() {
var controllers = {},
globals = false,
CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/;
globals = false;
/**
* @ngdoc method
@@ -87,7 +98,12 @@ function $ControllerProvider() {
}
if (isString(expression)) {
match = expression.match(CNTRL_REG),
match = expression.match(CNTRL_REG);
if (!match) {
throw $controllerMinErr('ctrlfmt',
"Badly formed controller string '{0}'. " +
"Must match `__name__ as __id__` or `__name__`.", expression);
}
constructor = match[1],
identifier = identifier || match[3];
expression = controllers.hasOwnProperty(constructor)
@@ -111,14 +127,22 @@ function $ControllerProvider() {
// Object creation: http://jsperf.com/create-constructor/2
var controllerPrototype = (isArray(expression) ?
expression[expression.length - 1] : expression).prototype;
instance = Object.create(controllerPrototype);
instance = Object.create(controllerPrototype || null);
if (identifier) {
addIdentifier(locals, identifier, instance, constructor || expression.name);
}
return extend(function() {
$injector.invoke(expression, instance, locals, constructor);
var instantiate;
return instantiate = extend(function() {
var result = $injector.invoke(expression, instance, locals, constructor);
if (result !== instance && (isObject(result) || isFunction(result))) {
instance = result;
if (identifier) {
// If result changed, re-assign controllerAs value to scope.
addIdentifier(locals, identifier, instance, constructor || expression.name);
}
}
return instance;
}, {
instance: instance,
+4 -1
View File
@@ -16,8 +16,11 @@
var htmlAnchorDirective = valueFn({
restrict: 'E',
compile: function(element, attr) {
if (!attr.href && !attr.xlinkHref && !attr.name) {
if (!attr.href && !attr.xlinkHref) {
return function(scope, element) {
// If the linked element is not an anchor tag anymore, do nothing
if (element[0].nodeName.toLowerCase() !== 'a') return;
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
'xlink:href' : 'href';
+31 -16
View File
@@ -68,7 +68,7 @@
}, 5000, 'page should navigate to /123');
});
xit('should execute ng-click but not reload when href empty string and name specified', function() {
it('should execute ng-click but not reload when href empty string and name specified', function() {
element(by.id('link-4')).click();
expect(element(by.model('value')).getAttribute('value')).toEqual('4');
expect(element(by.id('link-4')).getAttribute('href')).toBe('');
@@ -159,20 +159,23 @@
*
* @description
*
* We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
* This directive sets the `disabled` attribute on the element if the
* {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
*
* A special directive is necessary because we cannot use interpolation inside the `disabled`
* attribute. The following example would make the button enabled on Chrome/Firefox
* but not on older IEs:
*
* ```html
* <div ng-init="scope = { isDisabled: false }">
* <button disabled="{{scope.isDisabled}}">Disabled</button>
* <div ng-init="isDisabled = false">
* <button disabled="{{isDisabled}}">Disabled</button>
* </div>
* ```
*
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as disabled. (Their presence means true and their absence means false.)
* This is because the HTML specification does not require browsers to preserve the values of
* boolean attributes such as `disabled` (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
* The `ngDisabled` directive solves this problem for the `disabled` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
*
* @example
<example>
@@ -191,7 +194,7 @@
*
* @element INPUT
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
* then special attribute "disabled" will be set on the element
* then the `disabled` attribute will be set on the element
*/
@@ -341,22 +344,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
};
};
});
+9 -6
View File
@@ -163,6 +163,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
forEach(form.$error, function(value, name) {
form.$setValidity(name, null, control);
});
forEach(form.$$success, function(value, name) {
form.$setValidity(name, null, control);
});
arrayRemove(controls, control);
};
@@ -180,23 +183,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
addSetValidityMethod({
ctrl: this,
$element: element,
set: function(object, property, control) {
set: function(object, property, controller) {
var list = object[property];
if (!list) {
object[property] = [control];
object[property] = [controller];
} else {
var index = list.indexOf(control);
var index = list.indexOf(controller);
if (index === -1) {
list.push(control);
list.push(controller);
}
}
},
unset: function(object, property, control) {
unset: function(object, property, controller) {
var list = object[property];
if (!list) {
return;
}
arrayRemove(list, control);
arrayRemove(list, controller);
if (list.length === 0) {
delete object[property];
}
+96 -1686
View File
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
'use strict';
/**
* @ngdoc directive
* @name ngChange
*
* @description
* Evaluate the given expression when the user changes the input.
* The expression is evaluated immediately, unlike the JavaScript onchange event
* which only triggers at the end of a change (usually, when the user leaves the
* form element or presses the return key).
*
* The `ngChange` expression is only evaluated when a change in the input value causes
* a new value to be committed to the model.
*
* It will not be evaluated:
* * if the value returned from the `$parsers` transformation pipeline has not changed
* * if the input has continued to be invalid since the model will stay `null`
* * if the model is changed programmatically and not by a change to the input value
*
*
* Note, this directive requires `ngModel` to be present.
*
* @element input
* @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
* in input value.
*
* @example
* <example name="ngChange-directive" module="changeExample">
* <file name="index.html">
* <script>
* angular.module('changeExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.counter = 0;
* $scope.change = function() {
* $scope.counter++;
* };
* }]);
* </script>
* <div ng-controller="ExampleController">
* <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
* <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
* <label for="ng-change-example2">Confirmed</label><br />
* <tt>debug = {{confirmed}}</tt><br/>
* <tt>counter = {{counter}}</tt><br/>
* </div>
* </file>
* <file name="protractor.js" type="protractor">
* var counter = element(by.binding('counter'));
* var debug = element(by.binding('confirmed'));
*
* it('should evaluate the expression if changing from view', function() {
* expect(counter.getText()).toContain('0');
*
* element(by.id('ng-change-example1')).click();
*
* expect(counter.getText()).toContain('1');
* expect(debug.getText()).toContain('true');
* });
*
* it('should not evaluate the expression if changing from model', function() {
* element(by.id('ng-change-example2')).click();
* expect(counter.getText()).toContain('0');
* expect(debug.getText()).toContain('true');
* });
* </file>
* </example>
*/
var ngChangeDirective = valueFn({
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
ctrl.$viewChangeListeners.push(function() {
scope.$eval(attr.ngChange);
});
}
});
+4 -3
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)) {
@@ -141,8 +141,9 @@ function classDirective(name, selector) {
* new classes are added.
*
* @animations
* add - happens just before the class is applied to the element
* remove - happens just before the class is removed from the element
* **add** - happens just before the class is applied to the elements
*
* **remove** - happens just before the class is removed from the element
*
* @element ANY
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
+2 -2
View File
@@ -49,7 +49,7 @@
<select ng-model="template" ng-options="t.name for t in templates">
<option value="">(blank)</option>
</select>
url of the template: <tt>{{template.url}}</tt>
url of the template: <code>{{template.url}}</code>
<hr/>
<div class="slide-animate-container">
<div class="slide-animate" ng-include="template.url"></div>
@@ -173,7 +173,7 @@
* @name ngInclude#$includeContentError
* @eventType emit on the scope ngInclude was declared in
* @description
* Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299)
* Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
*
* @param {Object} angularEvent Synthetic event object.
* @param {String} src URL of content to load.
+1 -1
View File
@@ -19,7 +19,7 @@
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
* sure you have parenthesis for correct precedence:
* <pre class="prettyprint">
* <div ng-init="test1 = (data | orderBy:'name')"></div>
* `<div ng-init="test1 = (data | orderBy:'name')"></div>`
* </pre>
* </div>
*
+128
View File
@@ -0,0 +1,128 @@
'use strict';
/**
* @ngdoc directive
* @name ngList
*
* @description
* Text input that converts between a delimited string and an array of strings. The default
* delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
* delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
*
* The behaviour of the directive is affected by the use of the `ngTrim` attribute.
* * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
* list item is respected. This implies that the user of the directive is responsible for
* dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
* tab or newline character.
* * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
* when joining the list items back together) and whitespace around each list item is stripped
* before it is added to the model.
*
* ### Example with Validation
*
* <example name="ngList-directive" module="listExample">
* <file name="app.js">
* angular.module('listExample', [])
* .controller('ExampleController', ['$scope', function($scope) {
* $scope.names = ['morpheus', 'neo', 'trinity'];
* }]);
* </file>
* <file name="index.html">
* <form name="myForm" ng-controller="ExampleController">
* List: <input name="namesInput" ng-model="names" ng-list required>
* <span class="error" ng-show="myForm.namesInput.$error.required">
* Required!</span>
* <br>
* <tt>names = {{names}}</tt><br/>
* <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
* <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
* <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
* <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
* </form>
* </file>
* <file name="protractor.js" type="protractor">
* var listInput = element(by.model('names'));
* var names = element(by.exactBinding('names'));
* var valid = element(by.binding('myForm.namesInput.$valid'));
* var error = element(by.css('span.error'));
*
* it('should initialize to model', function() {
* expect(names.getText()).toContain('["morpheus","neo","trinity"]');
* expect(valid.getText()).toContain('true');
* expect(error.getCssValue('display')).toBe('none');
* });
*
* it('should be invalid if empty', function() {
* listInput.clear();
* listInput.sendKeys('');
*
* expect(names.getText()).toContain('');
* expect(valid.getText()).toContain('false');
* expect(error.getCssValue('display')).not.toBe('none');
* });
* </file>
* </example>
*
* ### Example - splitting on whitespace
* <example name="ngList-directive-newlines">
* <file name="index.html">
* <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
* <pre>{{ list | json }}</pre>
* </file>
* <file name="protractor.js" type="protractor">
* it("should split the text by newlines", function() {
* var listInput = element(by.model('list'));
* var output = element(by.binding('list | json'));
* listInput.sendKeys('abc\ndef\nghi');
* expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
* });
* </file>
* </example>
*
* @element input
* @param {string=} ngList optional delimiter that should be used to split the value.
*/
var ngListDirective = function() {
return {
restrict: 'A',
priority: 100,
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
// We want to control whitespace trimming so we use this convoluted approach
// to access the ngList attribute, which doesn't pre-trim the attribute
var ngList = element.attr(attr.$attr.ngList) || ', ';
var trimValues = attr.ngTrim !== 'false';
var separator = trimValues ? trim(ngList) : ngList;
var parse = function(viewValue) {
// If the viewValue is invalid (say required but empty) it will be `undefined`
if (isUndefined(viewValue)) return;
var list = [];
if (viewValue) {
forEach(viewValue.split(separator), function(value) {
if (value) list.push(trimValues ? trim(value) : value);
});
}
return list;
};
ctrl.$parsers.push(parse);
ctrl.$formatters.push(function(value) {
if (isArray(value)) {
return value.join(ngList);
}
return undefined;
});
// Override the standard $isEmpty because an empty array means the input is empty.
ctrl.$isEmpty = function(value) {
return !value || !value.length;
};
}
};
};
File diff suppressed because it is too large Load Diff
+645
View File
@@ -0,0 +1,645 @@
'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` **`disable when`** `disable` **`for`** `value` **`in`** `array`
* * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
* * `label` **`disable when`** `disable` **`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`
* * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`group by`** `group`
* **`for` `(`**`key`**`,`** `value`**`) in`** `object`
* * `select` **`as`** `label` **`disable when`** `disable`
* **`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.
* * `disable`: The result of this expression will be used to disable the rendered `<option>`
* element. Return `true` to disable.
* * `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', notAnOption: true},
{name:'red', shade:'dark'},
{name:'blue', shade:'dark', notAnOption: true},
{name:'yellow', shade:'light', notAnOption: false}
];
$scope.myColor = $scope.colors[2]; // red
}]);
</script>
<div ng-controller="ExampleController">
<ul>
<li ng-repeat="color in colors">
Name: <input ng-model="color.name">
<input type="checkbox" ng-model="color.notAnOption"> Disabled?
[<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/>
Color grouped by shade, with some disabled:
<select ng-model="myColor"
ng-options="color.name group by color.shade disable when color.notAnOption 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
// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\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: disable when expression (disableWhenFn)
// 5: array item variable name
// 6: object item key variable name
// 7: object item value variable name
// 8: collection expression
// 9: 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[5] || match[7];
// The variable name for the key of the item in the collection
var keyName = match[6];
// 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[9];
// 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 disableWhenFn = $parse(match[4] || '');
var valuesFn = $parse(match[8]);
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, disabled) {
this.selectValue = selectValue;
this.viewValue = viewValue;
this.label = label;
this.group = group;
this.disabled = disabled;
}
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 selectValue = getTrackByValue(values[key], locals);
watchedArray.push(selectValue);
// Only need to watch the displayFn if there is a specific label expression
if (match[2]) {
var label = displayFn(scope, locals);
watchedArray.push(label);
}
// Only need to watch the disableWhenFn if there is a specific disable expression
if (match[4]) {
var disableWhen = disableWhenFn(scope, locals);
watchedArray.push(disableWhen);
}
});
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 disabled = disableWhenFn(scope, locals);
var optionItem = new Option(selectValue, viewValue, label, group, disabled);
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 && !option.disabled) {
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 && !selectedOption.disabled) {
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.disabled) option.element.selected = true;
});
}
};
selectCtrl.readValue = function readNgOptionsMultiple() {
var selectedValues = selectElement.val() || [],
selections = [];
forEach(selectedValues, function(value) {
var option = options.selectValueMap[value];
if (!option.disabled) selections.push(option.viewValue);
});
return selections;
};
}
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;
element.disabled = option.disabled;
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;
}
});
+88 -11
View File
@@ -23,6 +23,80 @@
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
* This may be useful when, for instance, nesting ngRepeats.
*
*
* # Iterating over object properties
*
* It is possible to get `ngRepeat` to iterate over the properties of an object using the following
* syntax:
*
* ```js
* <div ng-repeat="(key, value) in myObj"> ... </div>
* ```
*
* You need to be aware that the JavaScript specification does not define what order
* it will return the keys for an object. (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.
*
*
* # Tracking and Duplicates
*
* When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
*
* * When an item is added, a new instance of the template is added to the DOM.
* * When an item is removed, its template instance is removed from the DOM.
* * When items are reordered, their respective templates are reordered in the DOM.
*
* By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
* there are duplicates, it is not possible to maintain a one-to-one mapping between collection
* items and DOM elements.
*
* If you do need to repeat duplicate items, you can substitute the default tracking behavior
* with your own using the `track by` expression.
*
* For example, you may track items by the index of each item in the collection, using the
* special scope property `$index`:
* ```html
* <div ng-repeat="n in [42, 42, 43, 43] track by $index">
* {{n}}
* </div>
* ```
*
* You may use arbitrary expressions in `track by`, including references to custom functions
* on the scope:
* ```html
* <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
* {{n}}
* </div>
* ```
*
* If you are working with objects that have an identifier property, you can track
* by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
* will not have to rebuild the DOM elements for items it has already rendered, even if the
* JavaScript objects in the collection have been substituted for new ones:
* ```html
* <div ng-repeat="model in collection track by model.id">
* {{model.name}}
* </div>
* ```
*
* When no `track by` expression is provided, it is equivalent to tracking by the built-in
* `$id` function, which tracks items by their identity:
* ```html
* <div ng-repeat="obj in collection track by $id(obj)">
* {{obj.prop}}
* </div>
* ```
*
* # Special repeat start and end points
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
@@ -90,12 +164,12 @@
*
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
*
* * `variable in expression track by tracking_expression` You can also provide an optional tracking function
* which can be used to associate the objects in the collection with the DOM elements. If no tracking function
* is specified the ng-repeat associates elements by identity in the collection. It is an error to have
* more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
* mapped to the same DOM element, which is not possible.) Filters should be applied to the expression,
* before specifying a tracking expression.
* * `variable in expression track by tracking_expression` You can also provide an optional tracking expression
* which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
* is specified, ng-repeat associates elements by identity. It is an error to have
* more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
* mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be
* applied before the tracking expression.
*
* For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
* will be associated by item identity in the array.
@@ -119,6 +193,11 @@
* For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after
* the items have been processed through the filter.
*
* Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax so it can be used only at the end
* (and not as operator, inside an expression).
*
* For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` .
*
* @example
* This example initializes the scope to a list of names and
* then uses `ngRepeat` to display every person:
@@ -267,7 +346,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var keyIdentifier = match[2];
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(aliasAs))) {
/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
aliasAs);
}
@@ -335,14 +414,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;
@@ -438,4 +516,3 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
}
};
}];
+3 -2
View File
@@ -40,10 +40,11 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
*
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS:
* class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
* with extra animation classes that can be added.
*
* ```css
* .ng-hide {
* .ng-hide:not(.ng-hide-animate) {
* /&#42; this is just another form of hiding an element &#42;/
* display: block!important;
* position: absolute;
+2 -2
View File
@@ -47,10 +47,10 @@
</example>
*/
var ngStyleDirective = ngDirective(function(scope, element, attr) {
scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
if (oldStyles && (newStyles !== oldStyles)) {
forEach(oldStyles, function(val, style) { element.css(style, '');});
}
if (newStyles) element.css(newStyles);
}, true);
});
});
-1
View File
@@ -130,7 +130,6 @@
*/
var ngSwitchDirective = ['$animate', function($animate) {
return {
restrict: 'EA',
require: 'ngSwitch',
// asks for $scope to fool the BC controller module
+206 -695
View File
@@ -1,6 +1,112 @@
'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) {
self.removeUnknownOption();
$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 +115,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 +139,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 +201,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 +248,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);
});
};
}
};
+91
View File
@@ -0,0 +1,91 @@
'use strict';
var requiredDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
attr.required = true; // force truthy in case we are on non input element
ctrl.$validators.required = function(modelValue, viewValue) {
return !attr.required || !ctrl.$isEmpty(viewValue);
};
attr.$observe('required', function() {
ctrl.$validate();
});
}
};
};
var patternDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var regexp, patternExp = attr.ngPattern || attr.pattern;
attr.$observe('pattern', function(regex) {
if (isString(regex) && regex.length > 0) {
regex = new RegExp('^' + regex + '$');
}
if (regex && !regex.test) {
throw minErr('ngPattern')('noregexp',
'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
regex, startingTag(elm));
}
regexp = regex || undefined;
ctrl.$validate();
});
ctrl.$validators.pattern = function(value) {
return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
};
}
};
};
var maxlengthDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var maxlength = -1;
attr.$observe('maxlength', function(value) {
var intVal = toInt(value);
maxlength = isNaN(intVal) ? -1 : intVal;
ctrl.$validate();
});
ctrl.$validators.maxlength = function(modelValue, viewValue) {
return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
};
}
};
};
var minlengthDirective = function() {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, elm, attr, ctrl) {
if (!ctrl) return;
var minlength = 0;
attr.$observe('minlength', function(value) {
minlength = toInt(value) || 0;
ctrl.$validate();
});
ctrl.$validators.minlength = function(modelValue, viewValue) {
return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
};
}
};
};
+8 -2
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;
@@ -186,7 +192,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
} else if (actualType === 'array') {
} else if (isArray(actual)) {
// In case `actual` is an array, consider it a match
// if ANY of it's items matches `expected`
return actual.some(function(item) {
+39 -22
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
@@ -226,8 +234,9 @@ function padNumber(num, digits, trim) {
}
num = '' + num;
while (num.length < digits) num = '0' + num;
if (trim)
if (trim) {
num = num.substr(num.length - digits);
}
return neg + num;
}
@@ -236,8 +245,9 @@ function dateGetter(name, size, offset, trim) {
offset = offset || 0;
return function(date) {
var value = date['get' + name]();
if (offset > 0 || value > -offset)
if (offset > 0 || value > -offset) {
value += offset;
}
if (value === 0 && offset == -12) value = 12;
return padNumber(value, size, trim);
};
@@ -252,8 +262,8 @@ function dateStrGetter(name, shortForm) {
};
}
function timeZoneGetter(date) {
var zone = -1 * date.getTimezoneOffset();
function timeZoneGetter(date, formats, offset) {
var zone = -1 * offset;
var paddedZone = (zone >= 0) ? "+" : "";
paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
@@ -353,7 +363,7 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
* * `'m'`: Minute in hour (0-59)
* * `'ss'`: Second in minute, padded (00-59)
* * `'s'`: Second in minute (0-59)
* * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
* * `'sss'`: Millisecond in second, padded (000-999)
* * `'a'`: AM/PM marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
* * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
@@ -383,7 +393,9 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
* specified in the string input, the time is considered to be in the local timezone.
* @param {string=} format Formatting rules (see Description). If not specified,
* `mediumDate` is used.
* @param {string=} timezone Timezone to be used for formatting. Right now, only `'UTC'` is supported.
* @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
* continental US time zone abbreviations, but for general use, use a time zone offset, for
* example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
* If not specified, the timezone of the browser will be used.
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
*
@@ -429,13 +441,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 +464,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;
}
@@ -474,13 +486,18 @@ function dateFilter($locale) {
}
}
if (timezone && timezone === 'UTC') {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
var dateTimezoneOffset = date.getTimezoneOffset();
if (timezone) {
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
if (!isNaN(requestedTimezoneOffset)) {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + dateTimezoneOffset - requestedTimezoneOffset);
dateTimezoneOffset = requestedTimezoneOffset;
}
}
forEach(parts, function(value) {
fn = DATE_FORMATS[value];
text += fn ? fn(date, $locale.DATETIME_FORMATS)
text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
});
+20 -10
View File
@@ -15,7 +15,10 @@
* @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.
* @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
* indicates an offset from the end of `input`. Defaults to `0`.
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
* had less than `limit` elements.
*
@@ -87,21 +90,28 @@
</example>
*/
function limitToFilter() {
return function(input, limit) {
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
return function(input, limit, begin) {
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);
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin;
if (limit >= 0) {
return input.slice(begin, begin + limit);
} else {
return isString(input) ? "" : [];
if (begin === 0) {
return input.slice(limit, input.length);
} else {
return input.slice(Math.max(0, begin + limit), begin);
}
}
};
}
+22 -16
View File
@@ -37,19 +37,24 @@ function isJsonLike(str) {
* @returns {Object} Parsed headers as key value object
*/
function parseHeaders(headers) {
var parsed = createMap(), key, val, i;
if (!headers) return parsed;
forEach(headers.split('\n'), function(line) {
i = line.indexOf(':');
key = lowercase(trim(line.substr(0, i)));
val = trim(line.substr(i + 1));
var parsed = createMap(), i;
function fillInParsed(key, val) {
if (key) {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
}
});
}
if (isString(headers)) {
forEach(headers.split('\n'), function(line) {
i = line.indexOf(':');
fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
});
} else if (isObject(headers)) {
forEach(headers, function(headerVal, headerKey) {
fillInParsed(lowercase(headerKey), trim(headerVal));
});
}
return parsed;
}
@@ -68,7 +73,7 @@ function parseHeaders(headers) {
* - if called with no arguments returns an object containing all headers.
*/
function headersGetter(headers) {
var headersObj = isObject(headers) ? headers : undefined;
var headersObj;
return function(name) {
if (!headersObj) headersObj = parseHeaders(headers);
@@ -98,8 +103,9 @@ function headersGetter(headers) {
* @returns {*} Transformed data.
*/
function transformData(data, headers, status, fns) {
if (isFunction(fns))
if (isFunction(fns)) {
return fns(data, headers, status);
}
forEach(fns, function(fn) {
data = fn(data, headers, status);
@@ -347,7 +353,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 +621,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 +840,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 +879,7 @@ function $HttpProvider() {
}
// execute if header value is a function for merged headers
return executeHeaderFns(reqHeaders);
return executeHeaderFns(reqHeaders, shallowCopy(config));
}
}
+22 -22
View File
@@ -88,6 +88,28 @@ function $InterpolateProvider() {
return '\\\\\\' + ch;
}
function unescapeText(text) {
return text.replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
}
function stringify(value) {
if (value == null) { // null || undefined
return '';
}
switch (typeof value) {
case 'string':
break;
case 'number':
value = '' + value;
break;
default:
value = toJson(value);
}
return value;
}
/**
* @ngdoc service
* @name $interpolate
@@ -243,23 +265,6 @@ function $InterpolateProvider() {
$sce.valueOf(value);
};
var stringify = function(value) {
if (value == null) { // null || undefined
return '';
}
switch (typeof value) {
case 'string':
break;
case 'number':
value = '' + value;
break;
default:
value = toJson(value);
}
return value;
};
return extend(function interpolationFn(context) {
var i = 0;
var ii = expressions.length;
@@ -294,11 +299,6 @@ function $InterpolateProvider() {
});
}
function unescapeText(text) {
return text.replace(escapedStartRegexp, startSymbol).
replace(escapedEndRegexp, endSymbol);
}
function parseStringifyInterceptor(value) {
try {
value = getValue(value);
+9 -6
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;
}
@@ -373,8 +373,9 @@ var locationPrototype = {
* @return {string} url
*/
url: function(url) {
if (isUndefined(url))
if (isUndefined(url)) {
return this.$$url;
}
var match = PATH_MATCH.exec(url);
if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
@@ -613,8 +614,9 @@ forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], fun
* @return {object} state
*/
Location.prototype.state = function(state) {
if (!arguments.length)
if (!arguments.length) {
return this.$$state;
}
if (Location !== LocationHtml5Url || !this.$$html5) {
throw $locationMinErr('nostate', 'History API state support is available only ' +
@@ -639,8 +641,9 @@ function locationGetter(property) {
function locationGetterSetter(property, preprocess) {
return function(value) {
if (isUndefined(value))
if (isUndefined(value)) {
return this[property];
}
this[property] = preprocess(value);
this.$$compose();
@@ -837,7 +840,7 @@ function $LocationProvider() {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
var elm = jqLite(event.target);
@@ -879,7 +882,7 @@ function $LocationProvider() {
// rewrite hashbang url <> html5 url
if ($location.absUrl() != initialUrl) {
if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
$browser.url($location.absUrl(), true);
}
+1271 -627
View File
File diff suppressed because it is too large Load Diff
+7 -8
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,8 +347,7 @@ function qFactory(nextTick, exceptionHandler) {
'qcycle',
"Expected promise to be resolved with value other than itself '{0}'",
val));
}
else {
} else {
this.$$resolve(val);
}

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