Compare commits

...

202 Commits

Author SHA1 Message Date
Matias Niemelä b6afe1b208 docs(CHANGELOG): add changes for 1.4.0-rc.0 2015-04-10 10:44:35 -07:00
Matias Niemelä c8700f04fb feat($animate): complete refactor of internal animation code
All of ngAnimate has been rewritten to make the internals of the
animation code more flexible, reuseable and performant.

BREAKING CHANGE: JavaSript and CSS animations can no longer be run in
parallel. With earlier versions of ngAnimate, both CSS and JS animations
would be run together when multiple animations were detected. This
feature has now been removed, however, the same effect, with even more
possibilities, can be achieved by injecting `$animateCss` into a
JavaScript-defined animation and creating custom CSS-based animations
from there. Read the ngAnimate docs for more info.

BREAKING CHANGE: The function params for `$animate.enabled()` when an
element is used are now flipped. This fix allows the function to act as
a getter when a single element param is provided.

```js
// < 1.4
$animate.enabled(false, element);

// 1.4+
$animate.enabled(element, false);
```

BREAKING CHANGE: In addition to disabling the children of the element,
`$animate.enabled(element, false)` will now also disable animations on
the element itself.

BREAKING CHANGE: Animation-related callbacks are now fired on
`$animate.on` instead of directly being on the element.

```js
// < 1.4
element.on('$animate:before', function(e, data) {
  if (data.event === 'enter') { ... }
});
element.off('$animate:before', fn);

// 1.4+
$animate.on(element, 'enter', function(data) {
  //...
});
$animate.off(element, 'enter', fn);
```

BREAKING CHANGE: There is no need to call `$scope.$apply` or
`$scope.$digest` inside of a animation promise callback anymore
since the promise is resolved within a digest automatically (but a
digest is not run unless the promise is chained).

```js
// < 1.4
$animate.enter(element).then(function() {
  $scope.$apply(function() {
    $scope.explode = true;
  });
});

// 1.4+
$animate.enter(element).then(function() {
  $scope.explode = true;
});
```

BREAKING CHANGE: When an enter, leave or move animation is triggered then it
will always end any pending or active parent class based animations
(animations triggered via ngClass) in order to ensure that any CSS
styles are resolved in time.
2015-04-09 14:44:54 -07:00
Pawel Kozlowski 73ab107a1e docs($http): clarify side effects of transformRequest functions
Closes #11438

Closes #11503
2015-04-06 12:25:35 +02:00
tomoyuki kashiro 9dfa949dad chore(version-info): modify versioning for < v1.0.2
Angular v1.0.1 and earlier did not have valid versions and had a different
docs url format, so you can not access their api docs from the version
drop-down.

Closes #11132
2015-04-03 07:28:33 +01:00
Shahar Talmi a057e0896a fix(cookieReader): safely access $document so it can be mocked
Closes #11373
Closes #11388
2015-04-03 07:14:38 +01:00
Martin Staffa 948120ecdb fix(ngModel): allow setting model to NaN when asyncValidator is present
Closes #11315
Closes #11411
2015-04-03 07:11:27 +01:00
Peter Bacon Darwin 7757f0a9e3 chore(dependencies): general update (including new dgeni-packages)
Closes #11095
2015-04-03 06:00:39 +01:00
Peter Bacon Darwin 560566f396 docs(toJson): improve option param documentation
With an upgrade to dgeni-packages 0.10.13, this style of optional param
is rendered more correctly.

See #11095
2015-04-03 06:00:17 +01:00
Peter Bacon Darwin d8d30ce676 style($browserSpec): fix typo 2015-04-02 21:47:10 +01:00
David Li d996305b44 perf($rootScope): remove history event handler when app is torn down
Remember the popstate and hashchange handler registered with window
when the application bootstraps, and remove it when the application
is torn down

Closes #9897
Closes #9905
2015-04-02 21:47:10 +01:00
Georgios Kalpakas 79fa7ddf8d test(browerTrigger): ensure touch events initialize correctly on touch enabled Chrome
On certain browsers (e.g. on desktop Chrome with touch-events enabled),
using the `initTouchEvent()` method (introduced in 06a9f0a) did not
correctly initialize the event, nor did the event get dispatched on
the target element.

Using the `Event` constructor and manually attaching a `TouchList`,
works around the issue (although not a proper fix).

Fixes #11471
Closes #11493
2015-04-02 21:09:57 +01:00
mustela 3621dbc100 fix($resource) add @ support for properties names
Add support for properties that starts with @. This is useful when working with BadgerFish convention.

Closes #10533
Closes #11473
2015-04-02 20:49:08 +03:00
Pawel Kozlowski 65bed615df docs($http): fix $httpParamSerializerJQLike description 2015-04-02 19:46:10 +02:00
Pawel Kozlowski 6c8464ad14 feat($http): support custom params serializers
Closes #3740
Closes #7429
Closes #9224
Closes #11461
2015-04-02 19:02:56 +02:00
Georgios Kalpakas 731c8b5e2d feat($anchorScroll): allow scrolling to a specified element
Add an optional argument to `$anchorScroll()` to enable scrolling to an
anchor element different than that related to the current value of
`$location.hash()`. If the argument is omitted or is not a string,
the value of `$location.hash()` will be used instead.

Closes #4568
Closes #9596
2015-04-02 19:52:54 +03:00
Michał Gołębiowski 06a9f0a95f fix(ngTouch): register touches properly when jQuery is used
If jQuery was used with Angular the touch logic was looking for touches
under the original event object. However, jQuery wraps all events, keeping
the original one under the originalEvent property and copies/normalizes some
of event properties. Not all properties are copied, e.g. touches which caused
them to not be recognized properly.

Thanks to @mcmar & @pomerantsev for original patch ideas.

Fixes #4001
Closes #8584
Closes #10797
Closes #11488
2015-04-02 14:05:26 +01:00
Michał Gołębiowski 2cdb2016b9 feat(travis): run unit tests on iOS 8
Refs #11471
Closes #11479
2015-04-02 13:26:45 +01:00
Lucas Galfaso 10ae33b2d8 fix($parse): fix parse errors on older Android WebViews which choke with reserved keywords
Closes #11455
2015-04-01 14:02:57 +02:00
Matias Niemelä c55a494433 fix(ngAnimate): ensure that minified repaint code isn't removed
Closes #9936
2015-03-31 14:04:45 -07:00
Georgios Kalpakas ffbeb32172 refactor(filterFilter): introduce helper function for "DRYness" 2015-03-31 19:54:36 +03:00
Georgios Kalpakas b5002ab62a fix(filterFilter): fix matching against null/undefined
Included fixes:

* Do not convert `null`/`undefined` to strings for substring matching in
  non-strict comparison mode. Prevents `null`/`undefined` from being
  matched against e.g. 'u'.
* Let `null` (as a top-level filter expression) match "deeply" (as do
  booleans, numbers and strings).
  E.g. let `filterFilter(arr, null)` match an item like `{someProp: null}`.

Closes #11432
Closes #11445
2015-03-31 19:54:07 +03:00
Georgios Kalpakas 393f50324c style(filterFilter): fix indentation and remove newline for consistency 2015-03-31 19:47:09 +03:00
Peter Bacon Darwin 0a9c4681c2 style(ngStyle): remove unused $log param 2015-03-31 16:33:47 +01:00
Peter Bacon Darwin 50bd7059e1 test(ngStyle): should cope with both '' and '0px' for initial height 2015-03-31 16:30:41 +01:00
Martin Staffa 36fd167e1d Revert "perf(ngStyleDirective): use $watchCollection"
This reverts commit 4c8d8ad508, because
it broke lazy one-time binding for object literals
(introduced in c024f28217)

Fixes #11403
2015-03-31 09:44:39 +02:00
Martin Staffa da75d138b1 test(ngStyle): ensure lazy one-time binding is supported
Closes #11405
2015-03-31 09:44:26 +02:00
Brent Dearth 171b9f7f23 perf(ngOptions): only perform deep equality check on ngModel if using track by
Closes #11448
Closes #11447
2015-03-30 22:58:29 +01:00
Georgios Kalpakas 73f3515ba7 refactor($compile): remove unused return statement
As discussed in
https://github.com/angular/angular.js/commit/89447b3f2b4c6db62c24473a81fedc3b04242b85#commitcomment-10280666.
2015-03-28 14:41:59 +00:00
Martin Staffa 7e5c447fa9 fix(select): don't call $render twice if $viewValue ref changes
Credits to @tepez for the fix

Closes #11329
Closes #11412
2015-03-25 18:39:55 +00:00
Peter Bacon Darwin 4ba43d2e6f style($$messageFormat) does not need to have a provider 2015-03-25 17:26:45 +00:00
Peter Bacon Darwin f353db9d86 docs(ngMessageFormat): module name is actually ngMessageFormat 2015-03-25 17:22:04 +00:00
Fred Sauer b2b33608a3 docs($route): add param info for $routeUpdate event
Closes #11419
2015-03-25 14:41:40 +00:00
Peter Bacon Darwin 448e789142 docs(misc/FAQ): fix typo 2015-03-25 14:02:49 +00:00
Jason Bedard b8dbdb0c5e perf(benchmark): add ngmodel benchmarks to largetable-bp
Closes #11082
2015-03-23 21:26:10 +00:00
Peter Bacon Darwin 67af519764 docs(FAQ): add info about Anglar 1's versioning strategy
See https://github.com/angular/angular.js/issues/10122#issuecomment-84139724
2015-03-23 12:09:39 +00:00
Peter Bacon Darwin 4f12ed0e4e docs(filters): clarify filter name restrictions
See #10122
2015-03-23 11:58:01 +00:00
Martin Staffa afd0807520 docs($compile): clarify link fn's controller argument
Also add "bindToController" to exampe directive definition object.

Closes #10815
2015-03-22 13:58:24 +01:00
Bradley Price 6a6f403f24 docs($http): remove trailing comma
Remove trailing comma to keep the same flow with all other code examples on page.
2015-03-22 13:05:07 +01:00
wiseleo 7811eadac7 docs(orderBy): replace operator = with ===
Fix documentation error on line 20 incorrectly mentioning
an assignment operator in a comparison operation.
Code on line 235 uses strict comparison operator.

Closes #11392
Closes #11393
2015-03-22 13:03:43 +01:00
Yuvraj Patil e5d1d6587d docs(guide/Unit Testing): update Jasmine's description
Jasmine is a "behavior-driven development framework",
not a "test-driven development framework"

Closes #11383
2015-03-21 14:36:50 +01:00
Martin Staffa 8c52f1daf4 docs(guide/scope): fix grammar
Closes #9829
2015-03-21 14:36:49 +01:00
Martin Staffa ee6a3ccd24 docs(guide/direcive): don't use shorthand in ddo
All the other examples use the full syntax.
Closes #11180
2015-03-21 14:36:47 +01:00
RaphStein e0e40e8c0a docs(ngAria): change aria-live attribute value from polite to assertive
For ngMessages directive ngAria generates aria-live with value assertive and not polite.

Closes #11280
2015-03-21 14:36:46 +01:00
Vojta Jina f8c8cf698a fix($rootScope): allow destroying a root scope
When running (i.e. bootstrapping and killing) multiple apps on the same page,
it makes sense to destroy the root scope.

Closes #11241
Closes #10895
2015-03-20 11:41:21 +00:00
Wesley Cho 9f7a80c53d docs($httpBackend): change to more friendly language
- Change s**t to more neutral word

Closes #11380
Closes #11364
2015-03-20 10:55:49 +00:00
Julie Ralph bea99e34a4 chore(ci): fix location of print logs from wait_for_browser_provider 2015-03-19 14:40:38 -07:00
Julie Ralph c7ebce6fb8 chore(test): bump Protractor version to 2.0.0 2015-03-19 13:27:05 -07:00
Peter Bacon Darwin 41428477ed docs(input[number]): clarify that model must be of type number
The docs also now link through to the error doc, which contains a runnable
example of how to work around this restriction.

Closes #11157
Closes #11334
2015-03-19 14:27:59 +00:00
Peter Bacon Darwin cc4213f03e chore(docs): improve error doc layout and linking
You can now link to an error by its name, namespace:name or error:namespace:name.
For example these would all link to https://docs.angularjs.org/error/$compile/ctreq

```
{@link ctreq}
{@link $compile:ctreq}
{@link error:$compile:ctreq}
```
2015-03-19 14:27:59 +00:00
Peter Bacon Darwin 7c9ad277ad docs(error/ngModel/numfmt): provide documentation for this error
See: https://github.com/angular/angular.js/commit/db044c408a7f8082758b96ab739348810c36e15a#commitcomment-7577199

Closes #11157
Closes #11334
2015-03-19 14:27:59 +00:00
Bob Fanger 4588e627bb feat(ngClass): add support for conditional map within an array.
This change allows `ngClass` expressions to have both objects and strings
within an array:

```js
$scope.classVar = 'nav-item';
$scope.activeVar = true;
```

```html
<div ng-class=" [classVar, {'is-active': activeVar }] ">
```

In this case, the CSS classes that will be added are: 'nav-item' and 'is-active'.

Closes #4807
2015-03-18 11:17:44 +00:00
Julie Ralph ea9fd82ce1 chore(ci): force travis to print logs after driver provider timeout
Travis does not do the after_script step if before_script fails,
so wait_for_browser_provider.sh was not printing out logs.
Force it to print them manually.
2015-03-17 16:57:55 -07:00
Martin Staffa 44337f63fa fix(ngAria): handle elements with role="checkbox/menuitemcheckbox"
Fixes #11317
Closes #11321
2015-03-17 20:53:55 +00:00
Julie Ralph 4b7a46adad chore(ci): make wait_for_browser_provider time out after 2 minutes
Before, if something went wrong, wait_for_browser_provider.sh would
wait indefinitely, and logs would never get printed. Now, we'll bail
early, and get some actual logs on what the problem was.

Closes #11350
2015-03-17 20:40:03 +00:00
Peter Bacon Darwin 1af563d43e fix($http): throw error if success and error methods do not receive a function
Closes #11330
Closes #11333
2015-03-17 19:13:37 +00:00
Chirayu Krishnappa 1e58488ad6 feat($interpolate): extend interpolation with MessageFormat like syntax
For more detailed information refer to this document:
https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit

**Example:**

```html

{{recipients.length, plural, offset:1
    =0 {You gave no gifts}
    =1 { {{ recipients[0].gender, select,
              male {You gave him a gift.}
              female {You gave her a gift.}
              other {You gave them a gift.}
          }}
       }
    one { {{ recipients[0].gender, select,
              male {You gave him and one other person a gift.}
              female {You gave her and one other person a gift.}
              other {You gave them and one other person a gift.}
          }}
       }
    other {You gave {{recipients[0].gender}} and # other people gifts. }
}}
```

This is a SEPARATE module so you MUST include `angular-messageformat.js`
or `angular-messageformat.min.js`.

In addition, your application module should depend on the "ngMessageFormat"
(e.g. angular.module('myApp', ['ngMessageFormat']);)

When you use the `ngMessageFormat`, the $interpolate gets overridden with
a new service that adds the new MessageFormat behavior.

**Syntax differences from MessageFormat:**

- MessageFormat directives are always inside `{{ }}` instead of
  single `{ }`.  This ensures a consistent interpolation syntax (else you
  could interpolate in more than one way and have to pick one based on
  the features availability for that syntax.)
- The first part of such a syntax can be an arbitrary Angular
  expression instead of a single identifier.
- You can nest them as deep as you want.  As mentioned earlier, you
  would use `{{ }}` to start the nested interpolation that may optionally
  include select/plural extensions.
- Only `select` and `plural` keywords are currently recognized.
- Quoting support is coming in a future commit.
- Positional arguments/placeholders are not supported. They don't make
  sense in Angular templates anyway (they are only helpful when using
  API calls from a programming language.)
- Redefining of the startSymbol (`{{`) and endSymbol (`}}`) used for
  interpolation is not yet supported.

Closes #11152
2015-03-17 16:17:56 +00:00
Wesley Cho 170ff9a37d fix($compile): throw error on invalid directive name
Directive names must start with a lower case letter.
Previously the compiler would quietly fail.
This change adds an assertion that fails if this is not the case.

Closes #11281
Closes #11109
2015-03-17 13:41:15 +00:00
rodneyebanks e57138d7ef feat(angular.Module): add decorator method
Closes #11305
Closes #11300
2015-03-17 12:59:29 +00:00
jasperSpeicher 9093efe062 docs(ngRepeat): improve the explanation of keys
Closes #11310
2015-03-17 12:54:02 +00:00
Caitlin Potter 8028315640 test($compile): make test cases more specific for #11326 2015-03-17 08:18:10 -04:00
James Talmage 9900610eea fix($compile): update data() when controller returns custom value
When controller functions return an explicit value that value should
be what is passed to the linking functions, and to any child/sibling
controllers that `require` it. It should also be bound to the data
store on the dom element.

Closes #11147
Closes #11326
2015-03-17 08:18:10 -04:00
Peter Bacon Darwin db866f1f86 docs(CHANGELOG): add changes for 1.4.0-beta.6 and 1.3.15 2015-03-17 12:06:22 +00:00
svershin d8492f4331 docs(misc/Downloading): update o latest stable version
Updated the CDN link and description to 1.3.14

Closes #11327
2015-03-15 21:00:39 +00:00
rodyhaddad 67688d5ca0 fix($sanitize): disallow unsafe svg animation tags
After #11124 got merged, a security vulnerability got introduced.
Animation in SVG became tolerated by the sanitizer.

Exploit Example:
```
<svg>
  <a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?">
    <circle r="400"></circle>
    <animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" />
  </a>
</svg>
```

Here we are animating an anchor's href, starting from a value that's a javascript URI,
allowing the executing of arbitrary javascript in the process.

Preventing only the animation of links is tricky, as SVG is weird and namespaces aren't predictable.
We've decided to have the sanitizer filter out svg animation tags instead.

Considering the sanitizer is commonly used to sanitize untrusted HTML code, this shouldn't affect
many apps in the wild. Also, no release has been with #11124 in it, but not this fix.

Closes #11290
2015-03-15 20:39:45 +00:00
rodyhaddad 9e8a687c37 chore(security): add warning banner to top of security sensitive files 2015-03-15 20:38:28 +00:00
Diego 3613a6007c refactor(ngPluralize):delete default directive restriction
delete the directive restriction to avoid code duplication since the default is already applied

Closes #11301
2015-03-13 21:10:57 +01:00
eeue56 2979bf38dd docs(guide/Services): Fix link to module.Factory 2015-03-13 19:54:22 +01:00
Amy 4c613a2603 docs(guide/Controllers): grammar and header fixes
fixed a comma and made the headers more logical.
2015-03-13 18:35:34 +01:00
Amy e6688ec280 docs(guide/Forms): format headers to match other docs
Primary header with page title, secondary headers for each description section.
2015-03-13 18:35:25 +01:00
Amy 4fdac4d22f docs(guide/Data Binding): add heading to page
the other pages have one, this makes them look more unified.
2015-03-13 18:35:11 +01:00
Matias Niemelä 2f4be3e2b9 chore(apis): add HashMap to the DI
HashMap will be used inside of angular-animate.js to store details of
ongoing animations on a per-element basis. Right now HashMap is only
available in core, but this patch will make it available to other areas.

Closes #11311
2015-03-13 12:40:15 -04:00
Julie Ralph 3086fcd644 chore(ci): bump sc version to 4.3.7 2015-03-13 09:35:17 -07:00
Peter Bacon Darwin 6a03ca2743 fix(ngOptions): fix model<->option interaction when using track by
This problem is beset by the problem of `ngModel` expecting models  to be
atomic things (primitives/objects).

> When it was first invented it was expected that ngModel would only be
a primitive, e.g. a string or a number. Later when things like ngList and
ngOptions were added or became more complex then various hacks were put
in place to make it look like it worked well with those but it doesn't.

-------------

Just to be clear what is happening, lets name the objects:

```js
var option1 = { uid: 1, name: 'someName1' };
var option2 = { uid: 2, name: 'someName2' };
var option3 = { uid: 3, name: 'someName3' };

var initialItem = { uid: 1, name: 'someName1' };

model {
  options: [option1, option2, option3],
  selected: initialItem
};
```

Now when we begin we have:

```js
expect(model.selected).toBe(initialItem);
expect(model.selected.uid).toEqual(option1.uid);
expect(model.selected).not.toBe(option1);
```

So although `ngOptions` has found a match between an option and the
modelValue, these are not the same object.

Now if we change the properties of the `model.selected` object, we are
effectively changing the `initialItem` object.

```js
model.selected.uid = 3;
model.selected.name = 'someName3';

expect(model.selected).toBe(initialItem);
expect(model.selected.uid).toEqual(option3.uid);
expect(model.selected).not.toBe(option3);
```

At the moment `ngModel` only watches for changes to the object identity
and so it doesn't trigger an update to the `ngOptions` directive.

This commit fixes this in `ngOptions` by adding a **deep** watch on the
`attr.ngModel` expression...

```js
scope.$watch(attr.ngModel, updateOptions, true);
```

You can see that in this Plunker:
http://plnkr.co/edit/0PE7qN5FXIA23y4RwyN0?p=preview

-------

But this isn't the end of the story. Since `ngModel` and `ngOptions` did
not make copies between the model and the view, we can't go around just
changing the properties of the `model.selected` object. This is particularly
important in the situation where the user has actually chosen an option,
since the `model.selected` points directly to one of the option objects:

```js
// User selects "someName2" option
expect(model.selected).toBe(option2);
expect(model.selected.uid).toEqual(option2.uid);
expect(model.selected).not.toBe(initialOption);
```

If we now change the `model.selected` object's properties we are actually
changing the `option2` object:

```js
expect(model.selected).toBe(option2);

model.selected.uid = 3;
model.selected.name = 'someName3';

expect(model.selected).toBe(option2);
expect(model.selected).not.toBe(option3);

expect(option2.uid).toEqual(3);
expect(option2.name).toEqual('someName3');
```

which means that the options are now broken:

```js
expect(model.options).toEqual([
  { uid: 1, name: 'someName1' },
  { uid: 3, name: 'someName3' },
  { uid: 3, name: 'someName3' }
]);
```

This commit fixes this in `ngOptions` by making copies when reading the
value if `track by` is being used. If we are not using `track by` then
we really do care about the identity of the object and should not be
copying...

You can see this in the Plunker here:
http://plnkr.co/edit/YEzEf4dxHTnoW5pbeJDp?p=preview

Closes #10869
Closes #10893
2015-03-13 11:55:01 +00:00
Peter Bacon Darwin 210c184184 style(ngOptionsSpec): ensure two newlines between specs 2015-03-13 11:43:49 +00:00
Izhaki 1846572d6f Added some extra sub-headings for clarity
Added `Normalization` and `Directive types`` subheadings to the `Matching directive` heading
2015-03-12 22:16:06 +01:00
Ciro Nunes 1c3bbada27 docs($templateCache): highlight the $templateCache service
Closes #11294
2015-03-12 22:14:43 +01:00
Devyn Stott 891b364c1b docs($log): Add debug button to example
Add debug button to example. It was in teh docs but not the example.

No breaking changes.
2015-03-12 21:45:09 +01:00
Marcin Wosinek 67297de109 docs(ngMessage): move up ngMessages link 2015-03-12 21:45:08 +01:00
Amy 68146cc092 docs(guide/Conceptual Overview): add a hyphen for clarity
Minor change, but the heading for "View independent business logic..."
would be more clear if it had a hyphen, "View-independent".
Perhaps it's because I'm new to javascript and its terminology,
but I read "View" as a verb rather than a noun on the first pass and
had to read on a bit to understand that it was, instead,
referring to The View. If you go just by grammar rules,
making "view independent" into the compound adjective,
"view-independent", makes it clear that it is modifying "business logic".
(http://www.grammarbook.com/punctuation/hyphens.asp - see Rule 1).
2015-03-12 21:44:59 +01:00
Peter Bacon Darwin c01b1f47c0 test(ngMock): test shallow copy of mock controller bindings
See #11239
2015-03-12 19:58:38 +00:00
Julie Ralph 1fc87e2b6a chore(ci): turn off verbose logging for sauce connect
We have not used the verbose data from these logs for months,
and it makes the Travis UI very slow.
2015-03-11 10:53:45 -07:00
Rouven Weßling 9b2a4c6851 test(IE8): remove workarounds for IE8 2015-03-11 16:53:29 +01:00
Peter Bacon Darwin 2b4dfa9e2b fix(date filter): display localised era for G format codes
This implementation is limited to displaying only AD (CE) years correctly,
since we do not support the `u` style year format that can be used to represent
dates before 1 AD.

Closes #10503
Closes #11266
2015-03-11 12:05:05 +00:00
Peter Bacon Darwin 68dbbfbf32 chore(ngLocale): regenerate locale files to include ERA info 2015-03-11 12:01:34 +00:00
Peter Bacon Darwin 4acb0af24c fix(ng/$locale): add ERA info in generic locale
This change also updates the closure i18n converter to pull in the ERA
info for generated locale files.
2015-03-11 12:01:33 +00:00
robiferentz bb5bf7f816 fix(jqLite): attr should ignore comment, text and attribute nodes
Follow jQuery handling of the `attr` function

Close #11038
2015-03-10 18:43:39 +01:00
Dave Jeffery 1924cf2216 docs(angular.extend): remove reference to non-existent parameter
The `deep` property is hard-coded to `false` and is not configurable
as the docs suggest.

Closes #11284
2015-03-10 13:26:41 +01:00
Rouven Weßling fb7db4a07b fix(rootScope): prevent memory leak when destroying scopes
Closes #11173
Closes #11169
2015-03-09 14:47:49 +00:00
gdi2290 4f1f9cfdb7 feat($interval): pass additional arguments to the callback
Similar to how [`setInterval`](http://mdn.io/setInterval#Syntax) works, this
commit allows users of `$interval` to add additional parameters to the call,
which will now be passed on to the callback function.

Closes #10632
2015-03-09 14:12:04 +00:00
gdi2290 3a4b6b83ef feat($timeout): pass additional arguments to the callback
Similar to how [`setTimeout`](mdn.io/setTimeout#Syntax) works, this commit
allows users of `$timeout` to add additional parameters to the call, which
will now be passed on to the callback function.

Closes #10631
2015-03-09 13:45:49 +00:00
bborowin 41fdb3d536 docs($compile): clarify when require will throw
To make things less confusing, explicitly state that require
WILL NOT throw a compile error if a link function is not specified.

Closes #11206
stackoverflow.com/questions/28730346/require-ddo-option-of-angular-directive-does-not-throw-an-error-when-it-should
2015-03-08 20:35:33 +01:00
Edward Delaporte 770a4ddcc6 docs(orderBy): Start with a simpler example.
Per the question raised at this Stack Overflow question:
http://stackoverflow.com/questions/24048590/angularjs-ng-repeat-orderby-date-not-working

The first example on this page is too complex to convey
the simplest possible case for using this function.

Closes #11144
2015-03-08 18:47:36 +01:00
Elliot Bentley f6b51fc0a2 docs(ngDisabled): Clarify "incorrect" example
Add obvious label to example of incorrect usage.
To a user scanning the docs (ie. me) it's easy to miss the fact
that this top example doesn't actually work.

Closes #11192
2015-03-08 18:14:17 +01:00
jmarkevicius f227f7a5af docs(guide/Animations): change *then* to *than* 2015-03-08 13:56:04 +01:00
Anthony Zotti 212975af96 docs(form): Add comma to line 319 for readability
Line 319 is hard to read on the first glimpse.

Currently the sentence reads:
"In Angular forms can be nested"

The sentence should read either
"In Angular, forms can be nested"
or
"Forms can be nested in angular"

I changed it to the former in this pull request.

Closes #11271
2015-03-08 13:56:03 +01:00
b0ri5 abf87673e5 docs(tutorial/0 - Bootstrapping): Add a "the" before "imperative / manual way"
I'm assuming "imperative / manual" is modifying "way" in which case I think "the" is needed.

I don't really know grammar, but as a native speaker it sounds odd without "the".

Closes #11269
2015-03-08 13:56:02 +01:00
Martin Staffa 248b036888 docs(tutorial/0 - Bootstrapping): clarify where the callback is registered
Closes #11270
2015-03-08 13:55:56 +01:00
Martin Staffa 3fd48742b0 test(ngJq): add e2e tests
Closes #11182
2015-03-06 13:51:40 +00:00
Martin Staffa 17f02d5bb2 chore(tests): don't rewrite urls for external scripts 2015-03-06 13:51:40 +00:00
Michel Boudreau 342e5f3ce3 fix(ngJq): don't rely on jqlite
Closes #11044
2015-03-06 13:51:39 +00:00
Peter Bacon Darwin 44e9d2ca6c docs(isNumber): fix link to isFinite 2015-03-06 11:54:55 +00:00
Peter Bacon Darwin 7705edc0da docs(isNumber): add info about using isFinite to exclude NaN
Closes #11230
2015-03-06 11:26:20 +00:00
Caitlin Potter d02d0585a0 feat(ngMock): allow mock $controller service to set up controller bindings
Adds a new mock for the $controller service, in order to simplify testing using the
bindToController feature.

```js
var dictionaryOfControllerBindings = {
  data: [
    { id: 0, phone: '...', name: '...' },
    { id: 1, phone: '...', name: '...' },
  ]
};

// When the MyCtrl constructor is called, `this.data ~= dictionaryOfControllerBindings.data`
$controller(MyCtrl, myLocals, dictionaryOfControllerBindings);
```

Closes #9425
Closes #11239
2015-03-06 10:54:14 +00:00
Peter Bacon Darwin 2d0eda10e4 docs(ngMessageExp): split ngMessage docs up to show its alias more clearly 2015-03-04 21:25:01 +00:00
gdi2290 4374f892c6 fix($animate): applyStyles from options on leave
Closes #10068
2015-03-04 14:26:10 +00:00
Peter Bacon Darwin caa0b9dab3 chore(privateMocks): use global angular to access helpers in they
When using `they` in modules such as `ngMessages` we do not have access to
the internal helper functions.
2015-03-04 13:14:03 +00:00
Matias Niemelä 0c541cfb2a chore(privateMocks): replace multiple occurrences of $prop for they() 2015-03-04 07:24:04 -05:00
Peter Bacon Darwin 2404b77e48 chore(gruntFile): add tthey and xthey to ddescribe-iit check 2015-03-04 12:20:49 +00:00
Peter Bacon Darwin 8783453784 style(privateMocks): remove unnecessary comment 2015-03-04 12:07:51 +00:00
Matias Niemelä e650c45894 feat(ngMock): add they helpers for testing multiple specs
There are now three new test helpers: `they`, `tthey` and `xthey`, which
will create multiple `it`, `iit` and `xit` blocks, respectively, parameterized
by each item in a collection that is passed.

(with tests and ammendments by @petebacondarwin)

Closes #10864
2015-03-04 12:05:18 +00:00
Steve Mao 82000111dc docs(CONTRIBUTING): add whitespaces for consistent styling
Closes #11214
2015-03-03 17:51:12 -07:00
Shahar Talmi ddc612056e refactor(filters/ngModel): extract common methods
Closes #11005
2015-03-03 14:57:03 +00:00
Shahar Talmi 0413bee8cc feat(ngModel): support conversion to timezone other than UTC
Closes #11005
2015-03-03 14:44:48 +00:00
Caitlin Potter c0498d45fe feat(angular.merge): provide an alternative to angular.extend that merges 'deeply'
Closes #10507
Closes #10519
2015-03-03 14:39:11 +00:00
Peter Bacon Darwin f591776313 test(filterFilter): improve tests related to custom toString 2015-03-03 14:13:13 +00:00
Martin R. Hufsky f8c4216170 feat(filterFilter): compare object with custom toString() to primitive
Closes #10464
Closes #10548
2015-03-03 14:09:23 +00:00
Peter Bacon Darwin 4501da327d docs(*): The Bootstrap CSS class alert-error is now alert-danger 2015-03-02 22:34:31 +00:00
Shahar Talmi 9b35dfb658 refactor($browser): remove private polling mechanism
The only feature of Angular using this mechanism was `$cookies`,
which no longer mirrors the browser cookie values and so does not
need to poll.

Closes #11222
2015-03-02 22:21:11 +00:00
Shahar Talmi 53c6636991 feat($cookiesProvider): provide path, domain, expires and secure options
This change provides properties on `$cookiesProvider` so that you can set the application
level default options for cookies that are set using the `$cookies` service
2015-03-02 22:20:00 +00:00
Shahar Talmi 92c366d205 feat($cookies): allow passing cookie options
The `put`, `putObject` and `remove` methods now take an options parameter
where you can provide additional options for the cookie value, such as `expires`,
`path`, `domain` and `secure`.

Closes #8324
Closes #3988
Closes #1786
Closes #950
2015-03-02 22:20:00 +00:00
Shahar Talmi 38fbe3ee83 feat($cookies): move logic into $cookies and deprecate $cookieStore
The new API on `$cookies` includes:

 * `get`
 * `put`
 * `getObject`
 * `putObject`
 * `getAll`
 * `remove`

The new API no longer polls the browser for changes to the cookies and no longer copy
cookie values onto the `$cookies` object.

The polling is expensive and caused issues with the `$cookies` properties not
synchronizing correctly with the actual browser cookie values.

The reason the polling was originally added was to allow communication between
different tabs, but there are better ways to do this today (for example `localStorage`).

DEPRECATION NOTICE:

`$cookieStore` is now deprecated as all the useful logic
has been moved to `$cookies`, to which `$cookieStore` now simply
delegates calls.

BREAKING CHANGE:

`$cookies` no longer exposes properties that represent the current browser cookie
values. Now you must explicitly the methods described above to access the cookie
values. This also means that you can no longer watch the `$cookies` properties for
changes to the browser's cookies.

This feature is generally only needed if a 3rd party library was programmatically
changing the cookies at runtime. If you rely on this then you must either write code that
can react to the 3rd party library making the changes to cookies or implement your own polling
mechanism.

Closes #6411
Closes #7631
2015-03-02 22:20:00 +00:00
Shahar Talmi 997fdea1ee refactor(ngCookies): split $cookies/$cookieStore to two files 2015-03-02 22:20:00 +00:00
Shahar Talmi 76b1b2bec2 refactor($browser): split cookie access into $$cookieReader and $$cookieWriter services 2015-03-02 22:19:59 +00:00
Casey Howard c62fa6bd89 fix(filterFilter): Fix filtering using an object expression when the filter value is undefined
Fixes #10419
Closes #10424
2015-03-02 22:16:21 +00:00
Caitlin Potter 3b8163b7b6 fix($browser): don't crash if history.state access causes error in IE
Reportedly, MSIE can throw under certain conditions when fetching this attribute.
We don't have a reliable reproduction for this but it doesn't do any real harm
to wrap access to this variable in a try-catch block.

Fixes #10367
Closes #10369
2015-03-02 20:29:41 +00:00
Brian Ford 2907a0288b docs(TRIAGING.md): improve process around PRs plz! label
Closes #10375
2015-03-02 19:40:54 +00:00
Marcy Sutton bb365070a3 feat(ngAria): add button role to ngClick
Closes #9254
Closes #10318
2015-03-02 13:48:18 +00:00
Peter Bacon Darwin 0f50b01cc7 test(aria): clean up test style and rename helper
Also removes unnecessary calls to `$apply`
2015-03-02 10:41:09 +00:00
Marcy Sutton 29cdaee2b6 feat(ngAria): add roles to custom inputs
This change adds the missing roles: `slider`, `radio`, `checkbox`

Closes #10012
Closes #10318
2015-03-02 10:40:22 +00:00
Jason Bedard 410f7c6826 fix(form): allow dynamic form names which initially evaluate to blank 2015-02-28 18:09:31 +01:00
Josh Kramer 9d071b2fc0 docs(ngModel): fix contenteditable description
contenteditable is supported in many more browsers than Angular itself is.

http://caniuse.com/#feat=contenteditable

Closes #11172
2015-02-28 18:02:37 +01:00
Marcin Wosinek 7cfa79e98e refactor(interpolate): remove redundant $watchGroup param
Closes #11021
2015-02-28 13:11:44 +01:00
Pawel Kozlowski f22e1fc961 fix(Angular): properly compare RegExp with other objects for equality
Fixes #11204

Closes #11205
2015-02-28 10:56:56 +01:00
Peter Bacon Darwin 80f139b860 chore(ngLocale): update locales with FIRSTDAYOFWEEK and WEEKENDRANGE 2015-02-27 21:53:10 +00:00
Santi Albo 3d149c7f20 feat(ngLocale): Add FIRSTDAYOFWEEK and WEEKENDRANGE from google data
Add more properties from Google closure localization data into ngLocale.
2015-02-27 21:48:31 +00:00
Dav 2c4ffd6af4 fix(filterFilter): do not throw an error if property is null when comparing objects
Closes #10991
Closes #10992
Closes #11116
2015-02-27 21:05:17 +00:00
Matias Niemelä d7ec5f392e fix(ngMessages): ensure that multi-level transclusion works with ngMessagesInclude
ngRepeat and any other directives that alter the DOM structure using
transclusion may cause ngMessagesInclude to behave in an unpredictable
manner. This fix ensures that the element containing the ngMessagesInclude
directive will stay in the DOM to avoid these issues.

Closes #11196
2015-02-26 23:52:56 -05:00
Peter Bacon Darwin bfd7b227db chore(code.angularjs.org/publish): push snapshot from jenkins master build
Since the CI server is not available, we are not able to pull the current
build from it to update the snapshot.

This commit changes Jenkins to push the snapshot directly
to the code.angularjs.org repository on every successful master build.
2015-02-25 11:03:53 +00:00
Julie Ralph 581ee9d0b6 chore(ci): update Karma to 0.12.32-beta.0
This will hopefully make the CI more stable because of its updated
version of socket.io.
2015-02-24 13:04:36 -08:00
Peter Bacon Darwin 57aa00e5dc docs(CHANGELOG): add changes for 1.4.0-beta.5 and 1.3.14 2015-02-24 17:51:15 +00:00
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
921 changed files with 41564 additions and 11180 deletions
+14 -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_.*$/
@@ -27,15 +33,21 @@ env:
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
+524 -2
View File
@@ -1,3 +1,517 @@
<a name="v1.4.0-rc.0"></a>
# v1.4.0-rc.0 smooth-unwinding (2015-04-10)
## Bug Fixes
- **$compile:**
- throw error on invalid directive name
([170ff9a3](https://github.com/angular/angular.js/commit/170ff9a37dea8772dda7c89e84176ac1a8992878),
[#11281](https://github.com/angular/angular.js/issues/11281), [#11109](https://github.com/angular/angular.js/issues/11109))
- update data() when controller returns custom value
([9900610e](https://github.com/angular/angular.js/commit/9900610eea4ece87b063f2aa9d82c75c369927df),
[#11147](https://github.com/angular/angular.js/issues/11147), [#11326](https://github.com/angular/angular.js/issues/11326))
- **$http:** throw error if `success` and `error` methods do not receive a function
([1af563d4](https://github.com/angular/angular.js/commit/1af563d43e74cb7be53e815b66fd91dd93986ed6),
[#11330](https://github.com/angular/angular.js/issues/11330), [#11333](https://github.com/angular/angular.js/issues/11333))
- **$parse:** fix parse errors on older Android WebViews which choke with reserved keywords
([10ae33b2](https://github.com/angular/angular.js/commit/10ae33b2d88b04df76f519edc50a47fa30f83e96),
[#11455](https://github.com/angular/angular.js/issues/11455))
- **$rootScope:** allow destroying a root scope
([f8c8cf69](https://github.com/angular/angular.js/commit/f8c8cf698aa23640249d79fd405605694478e4f7),
[#11241](https://github.com/angular/angular.js/issues/11241), [#10895](https://github.com/angular/angular.js/issues/10895))
- **cookieReader:** safely access $document so it can be mocked
([a057e089](https://github.com/angular/angular.js/commit/a057e0896a7fe2fdaba50b2515555b86e4f4be27),
[#11373](https://github.com/angular/angular.js/issues/11373), [#11388](https://github.com/angular/angular.js/issues/11388))
- **filterFilter:** fix matching against `null`/`undefined`
([b5002ab6](https://github.com/angular/angular.js/commit/b5002ab62ad6e13f4339e20106e1fdece14912a2),
[#11432](https://github.com/angular/angular.js/issues/11432), [#11445](https://github.com/angular/angular.js/issues/11445))
- **ngAnimate:** ensure that minified repaint code isn't removed
([c55a4944](https://github.com/angular/angular.js/commit/c55a494433e619aad0c7ef9fddadc0b3fdf53915),
[#9936](https://github.com/angular/angular.js/issues/9936))
- **ngAria:** handle elements with role="checkbox/menuitemcheckbox"
([44337f63](https://github.com/angular/angular.js/commit/44337f63fa94116795e83e3a764a6ba6782809c7),
[#11317](https://github.com/angular/angular.js/issues/11317), [#11321](https://github.com/angular/angular.js/issues/11321))
- **ngModel:** allow setting model to NaN when asyncValidator is present
([948120ec](https://github.com/angular/angular.js/commit/948120ecdbc4dd07880c0107564c50c7675b8a93),
[#11315](https://github.com/angular/angular.js/issues/11315), [#11411](https://github.com/angular/angular.js/issues/11411))
- **ngTouch:** register touches properly when jQuery is used
([06a9f0a9](https://github.com/angular/angular.js/commit/06a9f0a95f0e72fa2e9879fe8a49e9bf69986a5f),
[#4001](https://github.com/angular/angular.js/issues/4001), [#8584](https://github.com/angular/angular.js/issues/8584), [#10797](https://github.com/angular/angular.js/issues/10797), [#11488](https://github.com/angular/angular.js/issues/11488))
- **select:** don't call $render twice if $viewValue ref changes
([7e5c447f](https://github.com/angular/angular.js/commit/7e5c447fa9ad7d81cc818d6e79392c3e4a6b23a0),
[#11329](https://github.com/angular/angular.js/issues/11329), [#11412](https://github.com/angular/angular.js/issues/11412))
## Features
- **$anchorScroll:** allow scrolling to a specified element
([731c8b5e](https://github.com/angular/angular.js/commit/731c8b5e2d01a44aa91f967f1a6acbadb8005a8b),
[#4568](https://github.com/angular/angular.js/issues/4568), [#9596](https://github.com/angular/angular.js/issues/9596))
- **$animate:** complete refactor of internal animation code
([c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef))
- **$http:** support custom params serializers
([6c8464ad](https://github.com/angular/angular.js/commit/6c8464ad14dd308349f632245c1a064c9aae242a),
[#3740](https://github.com/angular/angular.js/issues/3740), [#7429](https://github.com/angular/angular.js/issues/7429), [#9224](https://github.com/angular/angular.js/issues/9224), [#11461](https://github.com/angular/angular.js/issues/11461))
- **$interpolate:** extend interpolation with MessageFormat like syntax
([1e58488a](https://github.com/angular/angular.js/commit/1e58488ad65abf7031bab5813523bb9d86dbd28c),
[#11152](https://github.com/angular/angular.js/issues/11152))
- **angular.Module:** add `decorator` method
([e57138d7](https://github.com/angular/angular.js/commit/e57138d7eff1210f99238c475fff57530bf0ab19),
[#11305](https://github.com/angular/angular.js/issues/11305), [#11300](https://github.com/angular/angular.js/issues/11300))
- **ngClass:** add support for conditional map within an array.
([4588e627](https://github.com/angular/angular.js/commit/4588e627bb7238b2113241919b948d0e5166c76d),
[#4807](https://github.com/angular/angular.js/issues/4807))
- **travis:** run unit tests on iOS 8
([2cdb2016](https://github.com/angular/angular.js/commit/2cdb2016b9d89abfb5ab988b67d5f26f3bf21908),
[#11479](https://github.com/angular/angular.js/issues/11479))
## Performance Improvements
- **$rootScope:** remove history event handler when app is torn down
([d996305b](https://github.com/angular/angular.js/commit/d996305b4470f80fbb1cbddf54b7d10ffbb6ab47),
[#9897](https://github.com/angular/angular.js/issues/9897), [#9905](https://github.com/angular/angular.js/issues/9905))
- **benchmark:** add ngmodel benchmarks to largetable-bp
([b8dbdb0c](https://github.com/angular/angular.js/commit/b8dbdb0c5e2cd176c6d94d60f781cfc02e646592),
[#11082](https://github.com/angular/angular.js/issues/11082))
- **ngOptions:** only perform deep equality check on ngModel if using track by
([171b9f7f](https://github.com/angular/angular.js/commit/171b9f7f2339ef9047b8526b2c3f36bb58d14feb),
[#11448](https://github.com/angular/angular.js/issues/11448), [#11447](https://github.com/angular/angular.js/issues/11447))
## Breaking Changes
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
JavaSript and CSS animations can no longer be run in
parallel. With earlier versions of ngAnimate, both CSS and JS animations
would be run together when multiple animations were detected. This
feature has now been removed, however, the same effect, with even more
possibilities, can be achieved by injecting `$animateCss` into a
JavaScript-defined animation and creating custom CSS-based animations
from there. Read the ngAnimate docs for more info.
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
The function params for `$animate.enabled()` when an
element is used are now flipped. This fix allows the function to act as
a getter when a single element param is provided.
```js
// < 1.4
$animate.enabled(false, element);
// 1.4+
$animate.enabled(element, false);
```
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
In addition to disabling the children of the element,
`$animate.enabled(element, false)` will now also disable animations on
the element itself.
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
Animation-related callbacks are now fired on
`$animate.on` instead of directly being on the element.
```js
// < 1.4
element.on('$animate:before', function(e, data) {
if (data.event === 'enter') { ... }
});
element.off('$animate:before', fn);
// 1.4+
$animate.on(element, 'enter', function(data) {
//...
});
$animate.off(element, 'enter', fn);
```
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
There is no need to call `$scope.$apply` or
`$scope.$digest` inside of a animation promise callback anymore
since the promise is resolved within a digest automatically (but a
digest is not run unless the promise is chained).
```js
// < 1.4
$animate.enter(element).then(function() {
$scope.$apply(function() {
$scope.explode = true;
});
});
// 1.4+
$animate.enter(element).then(function() {
$scope.explode = true;
});
```
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
When an enter, leave or move animation is triggered then it
will always end any pending or active parent class based animations
(animations triggered via ngClass) in order to ensure that any CSS
styles are resolved in time.
<a name="1.4.0-beta.6"></a>
# 1.4.0-beta.6 cookie-liberation (2015-03-17)
## Bug Fixes
- **$animate:** call `applyStyles` from options on `leave`
([4374f892](https://github.com/angular/angular.js/commit/4374f892c6fa4af6ba1f2ed47c5f888fdb5fadc5),
[#10068](https://github.com/angular/angular.js/issues/10068))
- **$browser:** don't crash if `history.state` access causes error in IE
([3b8163b7](https://github.com/angular/angular.js/commit/3b8163b7b664f24499e75460ab50c066eaec0f78),
[#10367](https://github.com/angular/angular.js/issues/10367), [#10369](https://github.com/angular/angular.js/issues/10369))
- **$sanitize:** disallow unsafe svg animation tags
([67688d5c](https://github.com/angular/angular.js/commit/67688d5ca00f6de4c7fe6084e2fa762a00d25610),
[#11290](https://github.com/angular/angular.js/issues/11290))
- **Angular:** properly compare RegExp with other objects for equality
([f22e1fc9](https://github.com/angular/angular.js/commit/f22e1fc9610ae111a3ea8746a3a57169c99ce142),
[#11204](https://github.com/angular/angular.js/issues/11204), [#11205](https://github.com/angular/angular.js/issues/11205))
- **date filter:** display localised era for `G` format codes
([2b4dfa9e](https://github.com/angular/angular.js/commit/2b4dfa9e2b63d7ebb78f3b0fd3439d18f932e1cd),
[#10503](https://github.com/angular/angular.js/issues/10503), [#11266](https://github.com/angular/angular.js/issues/11266))
- **filterFilter:**
- fix filtering using an object expression when the filter value is undefined
([c62fa6bd](https://github.com/angular/angular.js/commit/c62fa6bd898e1048d4690d41034489dc60ba6ac2),
[#10419](https://github.com/angular/angular.js/issues/10419), [#10424](https://github.com/angular/angular.js/issues/10424))
- do not throw an error if property is null when comparing objects
([2c4ffd6a](https://github.com/angular/angular.js/commit/2c4ffd6af4eb012c4054fe7c096267bbc5510af0),
[#10991](https://github.com/angular/angular.js/issues/10991), [#10992](https://github.com/angular/angular.js/issues/10992), [#11116](https://github.com/angular/angular.js/issues/11116))
- **form:** allow dynamic form names which initially evaluate to blank
([410f7c68](https://github.com/angular/angular.js/commit/410f7c682633c681be641cd2a321f9e51671d474))
- **jqLite:** attr should ignore comment, text and attribute nodes
([bb5bf7f8](https://github.com/angular/angular.js/commit/bb5bf7f8162d11610a53428e630b47030bdc38e5))
- **ng/$locale:** add ERA info in generic locale
([4acb0af2](https://github.com/angular/angular.js/commit/4acb0af24c7fb3705a197ca96adc532de4766a7a))
- **ngJq:** don't rely on existence of jqlite
([342e5f3c](https://github.com/angular/angular.js/commit/342e5f3ce38d2fd10c5d5a98ca66f864286a7922),
[#11044](https://github.com/angular/angular.js/issues/11044))
- **ngMessages:** ensure that multi-level transclusion works with `ngMessagesInclude`
([d7ec5f39](https://github.com/angular/angular.js/commit/d7ec5f392e1550658ddf271a30627b1749eccb69),
[#11196](https://github.com/angular/angular.js/issues/11196))
- **ngOptions:** fix model<->option interaction when using `track by`
([6a03ca27](https://github.com/angular/angular.js/commit/6a03ca274314352052c3082163367a146bb11c2d),
[#10869](https://github.com/angular/angular.js/issues/10869), [#10893](https://github.com/angular/angular.js/issues/10893))
- **rootScope:** prevent memory leak when destroying scopes
([fb7db4a0](https://github.com/angular/angular.js/commit/fb7db4a07bd1b0b67824d3808fe315419b272689),
[#11173](https://github.com/angular/angular.js/issues/11173), [#11169](https://github.com/angular/angular.js/issues/11169))
## Features
- **$cookies:**
- allow passing cookie options
([92c366d2](https://github.com/angular/angular.js/commit/92c366d205da36ec26502aded23db71a6473dad7),
[#8324](https://github.com/angular/angular.js/issues/8324), [#3988](https://github.com/angular/angular.js/issues/3988), [#1786](https://github.com/angular/angular.js/issues/1786), [#950](https://github.com/angular/angular.js/issues/950))
- move logic into $cookies and deprecate $cookieStore
([38fbe3ee](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe),
[#6411](https://github.com/angular/angular.js/issues/6411), [#7631](https://github.com/angular/angular.js/issues/7631))
- **$cookiesProvider:** provide path, domain, expires and secure options
([53c66369](https://github.com/angular/angular.js/commit/53c663699126815eabc2a3bc1e3bafc8b3874268))
- **$interval:** pass additional arguments to the callback
([4f1f9cfd](https://github.com/angular/angular.js/commit/4f1f9cfdb721cf308ca1162b2227836dc1d28388),
[#10632](https://github.com/angular/angular.js/issues/10632))
- **$timeout:** pass additional arguments to the callback
([3a4b6b83](https://github.com/angular/angular.js/commit/3a4b6b83efdb8051e5c4803c0892c19ceb2cba50),
[#10631](https://github.com/angular/angular.js/issues/10631))
- **angular.merge:** provide an alternative to `angular.extend` that merges 'deeply'
([c0498d45](https://github.com/angular/angular.js/commit/c0498d45feb913c318224ea70b5adf7112df6bac),
[#10507](https://github.com/angular/angular.js/issues/10507), [#10519](https://github.com/angular/angular.js/issues/10519))
- **filterFilter:** compare object with custom `toString()` to primitive
([f8c42161](https://github.com/angular/angular.js/commit/f8c421617096a8d613f4eb6d0f5b098ee149c029),
[#10464](https://github.com/angular/angular.js/issues/10464), [#10548](https://github.com/angular/angular.js/issues/10548))
- **ngAria:**
- add `button` role to `ngClick`
([bb365070](https://github.com/angular/angular.js/commit/bb365070a3ed7c2d26056d378ab6a8ef493b23cc),
[#9254](https://github.com/angular/angular.js/issues/9254), [#10318](https://github.com/angular/angular.js/issues/10318))
- add roles to custom inputs
([29cdaee2](https://github.com/angular/angular.js/commit/29cdaee2b6e853bc3f8882a00661698d146ecd18),
[#10012](https://github.com/angular/angular.js/issues/10012), [#10318](https://github.com/angular/angular.js/issues/10318))
- **ngLocale:** Add FIRSTDAYOFWEEK and WEEKENDRANGE from google data
([3d149c7f](https://github.com/angular/angular.js/commit/3d149c7f20ffabab5a635af9ddcfc7105112ab4a))
- **ngMock:**
- allow mock $controller service to set up controller bindings
([d02d0585](https://github.com/angular/angular.js/commit/d02d0585a086ecd2e1de628218b5a6d85c8fc7bd),
[#9425](https://github.com/angular/angular.js/issues/9425), [#11239](https://github.com/angular/angular.js/issues/11239))
- add `they` helpers for testing multiple specs
([e650c458](https://github.com/angular/angular.js/commit/e650c45894abe6314a806e6b3e32c908df5c00fd),
[#10864](https://github.com/angular/angular.js/issues/10864))
- **ngModel:** support conversion to timezone other than UTC
([0413bee8](https://github.com/angular/angular.js/commit/0413bee8cc563a6555f8d42d5f183f6fbefc7350),
[#11005](https://github.com/angular/angular.js/issues/11005))
## Breaking Changes
- **$cookies:** due to [38fbe3ee](https://github.com/angular/angular.js/commit/38fbe3ee8370fc449b82d80df07b5c2ed2cd5fbe),
`$cookies` no longer exposes properties that represent the current browser cookie
values. Now you must explicitly the methods described above to access the cookie
values. This also means that you can no longer watch the `$cookies` properties for
changes to the browser's cookies.
This feature is generally only needed if a 3rd party library was programmatically
changing the cookies at runtime. If you rely on this then you must either write code that
can react to the 3rd party library making the changes to cookies or implement your own polling
mechanism.
<a name="1.3.15"></a>
# 1.3.15 locality-filtration (2015-03-17)
## Bug Fixes
- **$animate:** call `applyStyles` with options on `leave`
([ebd84e80](https://github.com/angular/angular.js/commit/ebd84e8008f45ccaa84290f6da8c2a114fcfa8cd),
[#10068](https://github.com/angular/angular.js/issues/10068))
- **$browser:** don't crash if history.state access causes error in IE
([92767c09](https://github.com/angular/angular.js/commit/92767c098feaf8c58faf2d67f882305019d8160e),
[#10367](https://github.com/angular/angular.js/issues/10367), [#10369](https://github.com/angular/angular.js/issues/10369))
- **Angular:** properly compare RegExp with other objects for equality
([b8e8f9af](https://github.com/angular/angular.js/commit/b8e8f9af78f4ef3e556dd3cef6bfee35ad4cb82a),
[#11204](https://github.com/angular/angular.js/issues/11204), [#11205](https://github.com/angular/angular.js/issues/11205))
- **date filter:** display localised era for `G` format codes
([f2683f95](https://github.com/angular/angular.js/commit/f2683f956fcd3216eaa263db20b31e0d46338800),
[#10503](https://github.com/angular/angular.js/issues/10503), [#11266](https://github.com/angular/angular.js/issues/11266))
- **filterFilter:**
- fix filtering using an object expression when the filter value is `undefined`
([63b9956f](https://github.com/angular/angular.js/commit/63b9956faf4c3679c88a9401b8ccbb111c0294ee),
[#10419](https://github.com/angular/angular.js/issues/10419), [#10424](https://github.com/angular/angular.js/issues/10424))
- do not throw an error if property is null when comparing objects
([01161a0e](https://github.com/angular/angular.js/commit/01161a0e9fb1af93e9f06535aed8392ed7f116a4),
[#10991](https://github.com/angular/angular.js/issues/10991), [#10992](https://github.com/angular/angular.js/issues/10992), [#11116](https://github.com/angular/angular.js/issues/11116))
- **form:** allow dynamic form names which initially evaluate to blank
([190ea883](https://github.com/angular/angular.js/commit/190ea883c588d63f8b900a8de1d45c6c9ebb01ec),
[#11096](https://github.com/angular/angular.js/issues/11096))
- **ng/$locale:** add ERA info in generic locale
([57842530](https://github.com/angular/angular.js/commit/578425303f2480959da80f31920d08f277d42010))
- **rootScope:** prevent memory leak when destroying scopes
([528cf09e](https://github.com/angular/angular.js/commit/528cf09e3f78ad4e3bb6a329ebe315c4f29b4cdb),
[#11173](https://github.com/angular/angular.js/issues/11173), [#11169](https://github.com/angular/angular.js/issues/11169))
- **templateRequest:** avoid throwing syntax error in Android 2.3
([75abbd52](https://github.com/angular/angular.js/commit/75abbd525f07866fdcc6fb311802b8fe700af174),
[#11089](https://github.com/angular/angular.js/issues/11089), [#11051](https://github.com/angular/angular.js/issues/11051), [#11088](https://github.com/angular/angular.js/issues/11088))
## Features
- **ngAria:**
- add `button` role to `ngClick`
([b9ad91cf](https://github.com/angular/angular.js/commit/b9ad91cf1e86310a2d2bf13b29fa13a9b835e1ce),
[#9254](https://github.com/angular/angular.js/issues/9254), [#10318](https://github.com/angular/angular.js/issues/10318))
- add roles to custom inputs
([21369943](https://github.com/angular/angular.js/commit/21369943fafd577b36827a641b021b1c14cefb57),
[#10012](https://github.com/angular/angular.js/issues/10012), [#10318](https://github.com/angular/angular.js/issues/10318))
- **ngMock:**
- allow mock $controller service to set up controller bindings
([b3878a36](https://github.com/angular/angular.js/commit/b3878a36d9f8e56ad7be1eedb9691c9bd12568cb),
[#9425](https://github.com/angular/angular.js/issues/9425), [#11239](https://github.com/angular/angular.js/issues/11239))
- add `they` helpers for testing multiple specs
([7288be25](https://github.com/angular/angular.js/commit/7288be25a75d6ca6ac7eca05a7d6b12ccb3a22f8),
[#10864](https://github.com/angular/angular.js/issues/10864))
<a name="1.4.0-beta.5"></a>
# 1.4.0-beta.5 karmic-stabilization (2015-02-24)
## Bug Fixes
- **$http:** properly access request headers with mixed case
([5da1256f](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
[#10881](https://github.com/angular/angular.js/issues/10881), [#10883](https://github.com/angular/angular.js/issues/10883))
- **input:** create max and/or min validator regardless of initial value
([c211e7a5](https://github.com/angular/angular.js/commit/c211e7a5ad5f1fb8748125f14912aa8715081925),
[#10307](https://github.com/angular/angular.js/issues/10307), [#10327](https://github.com/angular/angular.js/issues/10327))
- **ngAria:** correctly set "checked" attr for checkboxes and radios
([d6eba217](https://github.com/angular/angular.js/commit/d6eba21733c6e67e90e3a4763d8d41ad89a73a0c),
[#10389](https://github.com/angular/angular.js/issues/10389), [#10212](https://github.com/angular/angular.js/issues/10212))
- **ngModel:** fix issues when parserName is same as validator key
([056a3170](https://github.com/angular/angular.js/commit/056a31700803c0a6014b43cfcc36c5c500cc596e),
[#10698](https://github.com/angular/angular.js/issues/10698), [#10850](https://github.com/angular/angular.js/issues/10850), [#11046](https://github.com/angular/angular.js/issues/11046))
- **ngOptions:** ngModel is optional
([ef894c87](https://github.com/angular/angular.js/commit/ef894c87eaead76d90169113ab6acc9287654ea3))
- **ngSanitize:** Do not ignore white-listed svg camelCased attributes
([46b80654](https://github.com/angular/angular.js/commit/46b80654cae9105642909cd55f73f7c26d2fbd80),
[#10779](https://github.com/angular/angular.js/issues/10779), [#10990](https://github.com/angular/angular.js/issues/10990), [#11124](https://github.com/angular/angular.js/issues/11124))
- **select:** remove unknown option when model is undefined and empty option is available
([30b48132](https://github.com/angular/angular.js/commit/30b48132e0fb92ea8dd25a9794b4c41a3a81a951),
[#11078](https://github.com/angular/angular.js/issues/11078), [#11092](https://github.com/angular/angular.js/issues/11092))
- **templateRequest:** avoid throwing syntax error in Android 2.3
([f6272333](https://github.com/angular/angular.js/commit/f6272333127d908b19da23f9cd8a74052711795b),
[#11089](https://github.com/angular/angular.js/issues/11089), [#11051](https://github.com/angular/angular.js/issues/11051), [#11088](https://github.com/angular/angular.js/issues/11088))
## Features
- **CommonJS:** - angular modules are now packaged for npm with helpful exports
- **limitTo:** extend the filter to take a beginning index argument
([aaae3cc4](https://github.com/angular/angular.js/commit/aaae3cc4160417e6dad802ed9d9f6d5471821a87),
[#5355](https://github.com/angular/angular.js/issues/5355), [#10899](https://github.com/angular/angular.js/issues/10899))
- **ngMessages:** provide support for dynamic message resolution
([c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
[#10036](https://github.com/angular/angular.js/issues/10036), [#9338](https://github.com/angular/angular.js/issues/9338))
- **ngOptions:** add support for disabling an option
([da9eac86](https://github.com/angular/angular.js/commit/da9eac8660343b1cd9fdcf9d2d1bda06067142d7),
[#638](https://github.com/angular/angular.js/issues/638), [#11017](https://github.com/angular/angular.js/issues/11017))
## Performance Improvements
- **$compile:**
- replace forEach(controller) with plain loops
([5b522867](https://github.com/angular/angular.js/commit/5b5228675f67c8f5e04c7183c3ef5e71cb2bf08b),
[#11084](https://github.com/angular/angular.js/issues/11084))
- avoid .data when fetching required controllers
([fa0aa839](https://github.com/angular/angular.js/commit/fa0aa83937378cf8fc720c38bcc5c78fc923624e))
- **ngOptions:** only watch labels if a display expression is specified
([51faaffd](https://github.com/angular/angular.js/commit/51faaffdbcc734c55d52ff6c42b386d5c90207ea))
## Breaking Changes
- **ngMessages:** due to [c9a4421f](https://github.com/angular/angular.js/commit/c9a4421fc3c97448527eadef1f42eb2f487ec2e0),
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>
```
- **$http:** due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
`transformRequest` functions can no longer modify request headers.
Before this commit `transformRequest` could modify request headers, ex.:
```javascript
function requestTransform(data, headers) {
headers = angular.extend(headers(), {
'X-MY_HEADER': 'abcd'
});
}
return angular.toJson(data);
}
```
This behavior was unintended and undocumented, so the change should affect very few applications. If one
needs to dynamically add / remove headers it should be done in a header function, for example:
```javascript
$http.get(url, {
headers: {
'X-MY_HEADER': function(config) {
return 'abcd'; //you've got access to a request config object to specify header value dynamically
}
}
})
```
<a name="1.3.14"></a>
# 1.3.14 instantaneous-browserification (2015-02-24)
## Features
- **CommonJS:** - angular modules are now packaged for npm with helpful exports
## Bug Fixes
- **input:** create max and/or min validator regardless of initial value
([abfce532](https://github.com/angular/angular.js/commit/abfce5327ce6fd29c33c62d2edf3600674a6b4c0),
[#10307](https://github.com/angular/angular.js/issues/10307), [#10327](https://github.com/angular/angular.js/issues/10327))
- **ngAria:** correctly set "checked" attr for checkboxes and radios
([944c150e](https://github.com/angular/angular.js/commit/944c150e6c3001e51d4bf5e2d8149ae4c565d1e3),
[#10389](https://github.com/angular/angular.js/issues/10389), [#10212](https://github.com/angular/angular.js/issues/10212))
- **ngModel:** fix issues when parserName is same as validator key
([6b7625a0](https://github.com/angular/angular.js/commit/6b7625a09508c4b5355121a9d4206a734b07b2e1),
[#10698](https://github.com/angular/angular.js/issues/10698), [#10850](https://github.com/angular/angular.js/issues/10850), [#11046](https://github.com/angular/angular.js/issues/11046))
<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)
@@ -13,7 +527,10 @@
- **$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:** remove references to last arguments to a fn call
- **$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
@@ -41,6 +558,9 @@
- **$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))
@@ -49,6 +569,8 @@
- **$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
@@ -88,7 +610,7 @@ is marked as optional and the attribute is not specified, no function will be ad
<a name="1.4.0-beta.2"></a>
# 1.4.0-beta.2 holographic-rooster (2015-01-26)
+4 -4
View File
@@ -1,4 +1,4 @@
#Contributing to AngularJS
# Contributing to AngularJS
We'd love for you to contribute to our source code and to make AngularJS even better than it is
today! Here are the guidelines we'd like you to follow:
@@ -54,7 +54,7 @@ For large fixes, please build and test the documentation before submitting the P
accidentally introduced any layout or formatting issues. You should also make sure that your commit message
is labeled "docs:" and follows the **Git Commit Guidelines** outlined below.
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly. When naming the commit, it is advised to still label it according to the commit guidelines below, by starting the commit message with **docs** and referencing the filename. Since this is not obvious and some changes are made on the fly, this is not strictly necessary and we will understand if this isn't done the first few times.
If you're just making a small change, don't worry about filing an issue first. Use the friendly blue "Improve this doc" button at the top right of the doc page to fork the repository in-place and make a quick change on the fly. When naming the commit, it is advised to still label it according to the commit guidelines below, by starting the commit message with **docs** and referencing the filename. Since this is not obvious and some changes are made on the fly, this is not strictly necessary and we will understand if this isn't done the first few times.
## <a name="submit"></a> Submission Guidelines
@@ -227,11 +227,11 @@ The subject contains succinct description of the change:
* don't capitalize first letter
* no dot (.) at the end
###Body
### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes"
The body should include the motivation for the change and contrast this with previous behavior.
###Footer
### Footer
The footer should contain any information about **Breaking Changes** and is also the place to
reference GitHub issues that this commit **Closes**.
+29 -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,
@@ -130,6 +126,9 @@ module.exports = function(grunt) {
ngLocale: {
files: { src: 'src/ngLocale/**/*.js' },
},
ngMessageFormat: {
files: { src: 'src/ngMessageFormat/**/*.js' },
},
ngMessages: {
files: { src: 'src/ngMessages/**/*.js' },
},
@@ -204,6 +203,10 @@ module.exports = function(grunt) {
dest: 'build/angular-resource.js',
src: util.wrap(files['angularModules']['ngResource'], 'module')
},
messageformat: {
dest: 'build/angular-messageFormat.js',
src: util.wrap(files['angularModules']['ngMessageFormat'], 'module')
},
messages: {
dest: 'build/angular-messages.js',
src: util.wrap(files['angularModules']['ngMessages'], 'module')
@@ -236,6 +239,7 @@ module.exports = function(grunt) {
animate: 'build/angular-animate.js',
cookies: 'build/angular-cookies.js',
loader: 'build/angular-loader.js',
messageformat: 'build/angular-messageFormat.js',
messages: 'build/angular-messages.js',
touch: 'build/angular-touch.js',
resource: 'build/angular-resource.js',
@@ -251,8 +255,19 @@ module.exports = function(grunt) {
'test/**/*.js',
'!test/ngScenario/DescribeSpec.js',
'!src/ng/directive/attrs.js', // legitimate xit here
'!src/ngScenario/**/*.js'
]
'!src/ngScenario/**/*.js',
'!test/helpers/privateMocks*.js'
],
options: {
disallowed: [
'iit',
'xit',
'tthey',
'xthey',
'ddescribe',
'xdescribe'
]
}
},
"merge-conflict": {
@@ -285,6 +300,10 @@ module.exports = function(grunt) {
},
shell: {
"npm-install": {
command: path.normalize('scripts/npm/install-dependencies.sh')
},
"promises-aplus-tests": {
options: {
stdout: false,
@@ -311,6 +330,10 @@ 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']);
+5 -1
View File
@@ -55,7 +55,11 @@ This process based on the idea of minimizing user pain
* inconvenience - causes ugly/boilerplate code in apps
1. Label `component: *`
* In rare cases, it's ok to have multiple components.
1. Label `PRs plz!` - These issues are good targets for PRs from the open source community. Apply to issues where the problem and solution are well defined in the comments, and it's not too complex.
1. Label `PRs plz!` - These issues are good targets for PRs from the open source community. In addition to applying this label, you must:
* Leave a comment explaining the problem and solution so someone can easily finish it.
* Assign the issue to yourself.
* Give feedback on PRs addressing this issue.
* You are responsible for mentoring contributors helping with this issue.
1. Label `origin: google` for issues from Google
1. Assign a milestone:
* Backlog - triaged fixes and features, should be the default choice
+25 -4
View File
@@ -40,6 +40,7 @@ var angularFiles = {
'src/ng/timeout.js',
'src/ng/urlUtils.js',
'src/ng/window.js',
'src/ng/cookieReader.js',
'src/ng/filter.js',
'src/ng/filter/filter.js',
@@ -86,10 +87,28 @@ var angularFiles = {
'angularModules': {
'ngAnimate': [
'src/ngAnimate/animate.js'
'src/ngAnimate/shared.js',
'src/ngAnimate/animateChildrenDirective.js',
'src/ngAnimate/animateCss.js',
'src/ngAnimate/animateCssDriver.js',
'src/ngAnimate/animateJs.js',
'src/ngAnimate/animateJsDriver.js',
'src/ngAnimate/animateQueue.js',
'src/ngAnimate/animateRunner.js',
'src/ngAnimate/animation.js',
'src/ngAnimate/module.js'
],
'ngCookies': [
'src/ngCookies/cookies.js'
'src/ngCookies/cookies.js',
'src/ngCookies/cookieStore.js',
'src/ngCookies/cookieWriter.js'
],
'ngMessageFormat': [
'src/ngMessageFormat/messageFormatCommon.js',
'src/ngMessageFormat/messageFormatSelector.js',
'src/ngMessageFormat/messageFormatInterpolationParts.js',
'src/ngMessageFormat/messageFormatParser.js',
'src/ngMessageFormat/messageFormatService.js'
],
'ngMessages': [
'src/ngMessages/messages.js'
@@ -162,7 +181,7 @@ var angularFiles = {
'src/publishExternalApis.js',
'@angularSrcModules',
'@angularScenario',
'@angularTest',
'@angularTest'
],
'karmaExclude': [
@@ -181,6 +200,7 @@ var angularFiles = {
'@angularSrcModules',
'src/ngScenario/browserTrigger.js',
'test/helpers/*.js',
'test/ngMessageFormat/*.js',
'test/ngMock/*.js',
'test/ngCookies/*.js',
'test/ngRoute/**/*.js',
@@ -197,7 +217,7 @@ var angularFiles = {
'src/publishExternalApis.js',
'@angularSrcModules',
'@angularScenario',
'@angularTest',
'@angularTest'
],
'karmaJqueryExclude': [
@@ -209,6 +229,7 @@ var angularFiles = {
angularFiles['angularSrcModules'] = [].concat(
angularFiles['angularModules']['ngAnimate'],
angularFiles['angularModules']['ngMessageFormat'],
angularFiles['angularModules']['ngMessages'],
angularFiles['angularModules']['ngCookies'],
angularFiles['angularModules']['ngResource'],
+17 -1
View File
@@ -20,6 +20,8 @@
<div>interpolation + fnInvocation: <input type="radio" ng-model="benchmarkType" value="interpolationFn"></div>
<div>ngBind + filter: <input type="radio" ng-model="benchmarkType" value="ngBindFilter"></div>
<div>interpolation + filter: <input type="radio" ng-model="benchmarkType" value="interpolationFilter"></div>
<div>ngModel (const name): <input type="radio" ng-model="benchmarkType" value="ngModelConstName"></div>
<div>ngModel (interp name): <input type="radio" ng-model="benchmarkType" value="ngModelInterpName"></div>
<ng-switch on="benchmarkType">
<baseline-binding-table ng-switch-when="baselineBinding">
@@ -84,7 +86,21 @@
<span ng-repeat="column in row">{{column.i | noop}}:{{column.j | noop}}|</span>
</div>
</div>
<div ng-switch-when="ngModelConstName">
<h2>ngModel (const name)</h2>
<div ng-repeat="row in data">
<input type="text" ng-model="row.i" name="constName" />
<input type="text" ng-model="row.j" />
</div>
</div>
<div ng-switch-when="ngModelInterpName">
<h2>ngModel (interp name)</h2>
<div ng-repeat="(rowIdx, row) in data">
<input type="text" ng-model="row.i" name="input-{{rowIdx}}" />
<input type="text" ng-model="row.j" name="input2-{{rowIdx}}" />
</div>
</div>
</ng-switch>
</div>
</div>
</div>
</div>
+8
View File
@@ -9,3 +9,11 @@
ng\:form {
display: block;
}
.ng-animate-shim {
visibility:hidden;
}
.ng-animate-anchor {
position:absolute;
}
+2 -1
View File
@@ -415,7 +415,7 @@ iframe.example {
.main-body-grid .side-navigation {
position:relative;
padding-bottom:50px;
padding-bottom:120px;
}
.main-body-grid .side-navigation.ng-hide {
@@ -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;
+8 -6
View File
@@ -21,11 +21,13 @@ angular.module('versions', [])
};
$scope.jumpToDocsVersion = function(version) {
var currentPagePath = $location.path().replace(/\/$/, '');
// TODO: We need to do some munging of the path for different versions of the API...
$window.location = version.docsUrl + currentPagePath;
var currentPagePath = $location.path().replace(/\/$/, ''),
url = '';
if (version.isOldDocsUrl) {
url = version.docsUrl;
}else{
url = version.docsUrl + currentPagePath;
}
$window.location = url;
};
}]);
+8 -2
View File
@@ -125,10 +125,16 @@ module.exports = new Package('angularjs', [
});
computeIdsProcessor.idTemplates.push({
docTypes: ['error', 'errorNamespace'],
docTypes: ['error'],
getId: function(doc) { return 'error:' + doc.namespace + ':' + doc.name; },
getAliases: function(doc) { return [doc.name, doc.namespace + ':' + doc.name, doc.id]; }
},
{
docTypes: ['errorNamespace'],
getId: function(doc) { return 'error:' + doc.name; },
getAliases: function(doc) { return [doc.id]; }
});
}
);
})
.config(function(checkAnchorLinksProcessor) {
+1 -1
View File
@@ -1,7 +1,7 @@
{% extends "base.template.html" %}
{% block content %}
<h1>Error: {$ doc.id $}
<h1>Error: {$ doc.namespace $}:{$ doc.name $}
<div><span class='hint'>{$ doc.fullName $}</span></div>
</h1>
+1 -1
View File
@@ -140,7 +140,7 @@ or JavaScript callbacks.
{@link ngAnimate#service Services / Factories}
</td>
<td>
Use {@link ngAnimate.$animate $animate} to trigger animation operations within your directive code.
Use {@link ng.$animate $animate} to trigger animation operations within your directive code.
</td>
</tr>
<tr>
+8
View File
@@ -0,0 +1,8 @@
@ngdoc error
@name $compile:baddir
@fullName Invalid Directive Name
@description
This error occurs when the name of a directive is not valid.
Directives must start with a lowercase character.
+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.
}]);
```
@@ -0,0 +1,6 @@
@ngdoc error
@name $interpolate:badexpr
@fullName Expecting end operator
@description
The Angular expression is missing the corresponding closing operator.
@@ -0,0 +1,11 @@
@ngdoc error
@name $interpolate:dupvalue
@fullName Duplicate choice in plural/select
@description
You have repeated a match selection for your plural or select MessageFormat
extension in your interpolation expression. The different choices have to be unique.
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,12 @@
@ngdoc error
@name $interpolate:logicbug
@fullName Bug in ngMessageFormat module
@description
You've just hit a bug in the ngMessageFormat module provided by angular-messageFormat.min.js.
Please file a github issue for this and provide the interpolation text that caused you to hit this
bug mentioning the exact version of AngularJS used and we will fix it!
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,17 @@
@ngdoc error
@name $interpolate:nochgmustache
@fullName Redefinition of start/endSymbol incompatible with MessageFormat extensions
@description
You have redefined `$interpolate.startSymbol`/`$interpolate.endSymbol` and also
loaded the `ngMessageFormat` module (provided by angular-messageFormat.min.js)
while creating your injector.
`ngMessageFormat` currently does not support redefinition of the
startSymbol/endSymbol used by `$interpolate`. If this is affecting you, please
file an issue and mention @chirayuk on it. This is intended to be fixed in a
future commit and the github issue will help gauge urgency.
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,12 @@
@ngdoc error
@name $interpolate:reqarg
@fullName Missing required argument for MessageFormat
@description
You must specify the MessageFormat function that you're using right after the
comma following the Angular expression. Currently, the supported functions are
"plural" and "select" (for gender selections.)
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,11 @@
@ngdoc error
@name $interpolate:reqcomma
@fullName Missing comma following MessageFormat plural/select keyword
@description
The MessageFormat syntax requires a comma following the "plural" or "select"
extension keyword in the extended interpolation syntax.
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,11 @@
@ngdoc error
@name $interpolate:reqendbrace
@fullName Unterminated message for plural/select value
@description
The plural or select message for a value or keyword choice has no matching end
brace to mark the end of the message.
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,6 @@
@ngdoc error
@name $interpolate:reqendinterp
@fullName Unterminated interpolation
@description
The interpolation text does not have an ending `endSymbol` ("}}" by default) and is unterminated.
@@ -0,0 +1,12 @@
@ngdoc error
@name $interpolate:reqopenbrace
@fullName An opening brace was expected but not found
@description
The plural or select extension keyword or values (such as "other", "male",
"female", "=0", "one", "many", etc.) MUST be followed by a message enclosed in
braces.
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,13 @@
@ngdoc error
@name $interpolate:reqother
@fullName Required choice "other" for select/plural in MessageFormat
@description
Your interpolation expression with a MessageFormat extension for either
"plural" or "select" (typically used for gender selection) does not contain a
message for the choice "other". Using either select or plural MessageFormat
extensions require that you provide a message for the selection "other".
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,12 @@
@ngdoc error
@name $interpolate:unknarg
@fullName Unrecognized MessageFormat extension
@description
The MessageFormat extensions provided by `ngMessageFormat` are currently
limited to "plural" and "select". The extension that you have used is either
unsupported or invalid.
For more information about the MessageFormat syntax in interpolation
expressions, please refer to MessageFormat extensions section at
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,10 @@
@ngdoc error
@name $interpolate:unsafe
@fullName MessageFormat extensions not allowed in secure context
@description
You have attempted to use a MessageFormat extension in your interpolation expression that is marked as a secure context. For security purposes, this is not supported.
Read more about secure contexts at {@link ng.$sce Strict Contextual Escaping
(SCE)} and about the MessageFormat extensions at {@link
guide/i18n#MessageFormat Angular i18n MessageFormat}.
@@ -0,0 +1,6 @@
@ngdoc error
@name $interpolate:untermstr
@fullName Unterminated string literal
@description
The string literal was not terminated in your Angular expression.
@@ -0,0 +1,8 @@
@ngdoc error
@name $interpolate:wantstring
@fullName Expected the beginning of a string
@description
We expected to see the beginning of a string (either a single quote or a double
quote character) in the expression but it was not found. The expression is
invalid. If this is incorrect, please file an issue on github.
+56
View File
@@ -0,0 +1,56 @@
@ngdoc error
@name ngModel:numfmt
@fullName Model is not of type `number`
@description
The number input directive `<input type="number">` requires the model to be a `number`.
If the model is something else, this error will be thrown.
Angular does not set validation errors on the `<input>` in this case
as this error is caused by incorrect application logic and not by bad input from the user.
If your model does not contain actual numbers then it is up to the application developer
to use a directive that will do the conversion in the `ngModel` `$formatters` and `$parsers`
pipeline.
## Example
In this example, our model stores the number as a string, so we provide the `stringToNumber`
directive to convert it into the format the `input[number]` directive expects.
<example module="numfmt-error-module">
<file name="index.html">
<table>
<tr ng-repeat="x in ['0', '1']">
<td>
<input type="number" string-to-number ng-model="x" /> {{ x }} : {{ typeOf(x) }}
</td>
</tr>
</table>
</file>
<file name="app.js">
angular.module('numfmt-error-module', [])
.run(function($rootScope) {
$rootScope.typeOf = function(value) {
return typeof value;
};
})
.directive('stringToNumber', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$parsers.push(function(value) {
return '' + value;
});
ngModel.$formatters.push(function(value) {
return parseFloat(value, 10);
});
}
};
});
</file>
</example>
+13 -7
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,16 @@ 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.
To fix widespread accessibility problems with `ng-click` on div elements, ngAria will dynamically
bind keypress by default as long as the element isn't an anchor, button, input or textarea.
You can turn this functionality on or off with the `bindKeypress` configuration option. ngAria
will also add the `button` role to communicate to users of assistive technologies.
For `ng-dblclick`, you must still manually add `ng-keypress` and role to non-interactive elements such
as `div` or `taco-button` to enable keyboard access.
<h3>Example</h3>
```html
@@ -223,13 +230,12 @@ 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>
The new ngMessages module makes it easy to display form validation or other messages with priority
sequencing and animation. To expose these visual messages to screen readers,
ngAria injects `aria-live="polite"`, causing them to be read aloud any time a message is shown,
ngAria injects `aria-live="assertive"`, causing them to be read aloud any time a message is shown,
regardless of the user's focus location.
###Example
@@ -243,7 +249,7 @@ regardless of the user's focus location.
Becomes:
```html
<div ng-messages="myForm.myName.$error" aria-live="polite">
<div ng-messages="myForm.myName.$error" aria-live="assertive">
<div ng-message="required">You did not enter a field</div>
<div ng-message="maxlength">Your field is too long</div>
</div>
+3 -3
View File
@@ -236,7 +236,7 @@ The example below shows how to perform animations during class changes:
</file>
</example>
Although the CSS is a little different then what we saw before, the idea is the same.
Although the CSS is a little different than what we saw before, the idea is the same.
## Which directives support animations?
@@ -253,7 +253,7 @@ The table below explains in detail which animation events are triggered
| {@link ng.directive:ngClass#animations ngClass or &#123;&#123;class&#125;&#125;} | add and remove |
| {@link ng.directive:ngShow#animations ngShow & ngHide} | add and remove (the ng-hide class value) |
For a full breakdown of the steps involved during each animation event, refer to the {@link ngAnimate.$animate API docs}.
For a full breakdown of the steps involved during each animation event, refer to the {@link ng.$animate API docs}.
## How do I use animations in my own directives?
@@ -276,6 +276,6 @@ myModule.directive('my-directive', ['$animate', function($animate) {
## More about animations
For a full breakdown of each method available on `$animate`, see the {@link ngAnimate.$animate API documentation}.
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
To see a complete demo, see the {@link tutorial/step_12 animation step within the AngularJS phonecat tutorial}.
+16 -16
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.
@@ -179,11 +179,11 @@ The following graphic shows how everything works together after we introduced th
<img style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-databinding2.png">
## View independent business logic: Services
## 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.
+7 -7
View File
@@ -30,12 +30,12 @@ Do not use controllers to:
services} instead.
- Manage the life-cycle of other components (for example, to create service instances).
# Setting up the initial state of a `$scope` object
## Setting up the initial state of a `$scope` object
Typically, when you create an application you need to set up the initial state for the Angular
`$scope`. You set up the initial state of a scope by attaching properties to the `$scope` object.
The properties contain the **view model** (the model that will be presented by the view). All the
`$scope` properties will be available to the template at the point in the DOM where the Controller
`$scope` properties will be available to the {@link templates template} at the point in the DOM where the Controller
is registered.
The following example demonstrates creating a `GreetingController`, which attaches a `greeting`
@@ -69,13 +69,13 @@ now be data-bound to the template:
```
# Adding Behavior to a Scope Object
## Adding Behavior to a Scope Object
In order to react to events or execute computation in the view we must provide behavior to the
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',[]);
@@ -99,7 +99,7 @@ objects (or primitives) assigned to the scope become model properties. Any metho
the scope are available in the template/view, and can be invoked via angular expressions
and `ng` event handler directives (e.g. {@link ng.directive:ngClick ngClick}).
# Using Controllers Correctly
## Using Controllers Correctly
In general, a Controller shouldn't try to do too much. It should contain only the business logic
needed for a single view.
@@ -125,7 +125,7 @@ following components:
- A model consisting of a string named `spice`
- A Controller with two functions that set the value of `spice`
The message in our template contains a binding to the `spice` model, which by default is set to the
The message in our template contains a binding to the `spice` model which, by default, is set to the
string "very". Depending on which button is clicked, the `spice` model is set to `chili` or
`jalapeño`, and the message is automatically updated by data-binding.
@@ -259,7 +259,7 @@ Inheritance works with methods in the same way as it does with properties. So in
examples, all of the properties could be replaced with methods that return string values.
## Testing Controllers
# Testing Controllers
Although there are many ways to test a Controller, one of the best conventions, shown below,
involves injecting the {@link ng.$rootScope $rootScope} and {@link ng.$controller $controller}:
+2
View File
@@ -3,6 +3,8 @@
@sortOrder 210
@description
# Data Binding
Data-binding in Angular apps is the automatic synchronization of data between the model and view
components. The way that Angular implements data-binding lets you treat the model as the
single-source-of-truth in your application. The view is a projection of the model at all times.
+33 -27
View File
@@ -54,6 +54,8 @@ The following also **matches** `ngModel`:
<input data-ng:model="foo">
```
### Normalization
Angular **normalizes** an element's tag and attribute name to determine which elements match which
directives. We typically refer to directives by their case-sensitive
[camelCase](http://en.wikipedia.org/wiki/CamelCase) **normalized** name (e.g. `ngModel`).
@@ -100,6 +102,8 @@ If you want to use an HTML validating tool, you can instead use the `data`-prefi
The other forms shown above are accepted for legacy reasons but we advise you to avoid them.
</div>
### Directive types
`$compile` can match directives based on element names, attributes, class names, as well as comments.
All of the Angular-provided directives match attribute name, tag name, comments, or class name.
@@ -827,37 +831,39 @@ element?
<file name="script.js">
angular.module('dragModule', [])
.directive('myDraggable', ['$document', function($document) {
return function(scope, element, attr) {
var startX = 0, startY = 0, x = 0, y = 0;
return {
link: function(scope, element, attr) {
var startX = 0, startY = 0, x = 0, y = 0;
element.css({
position: 'relative',
border: '1px solid red',
backgroundColor: 'lightgrey',
cursor: 'pointer'
});
element.on('mousedown', function(event) {
// Prevent default dragging of selected content
event.preventDefault();
startX = event.pageX - x;
startY = event.pageY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
y = event.pageY - startY;
x = event.pageX - startX;
element.css({
top: y + 'px',
left: x + 'px'
position: 'relative',
border: '1px solid red',
backgroundColor: 'lightgrey',
cursor: 'pointer'
});
}
function mouseup() {
$document.off('mousemove', mousemove);
$document.off('mouseup', mouseup);
element.on('mousedown', function(event) {
// Prevent default dragging of selected content
event.preventDefault();
startX = event.pageX - x;
startY = event.pageY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
y = event.pageY - startY;
x = event.pageX - startX;
element.css({
top: y + 'px',
left: x + 'px'
});
}
function mouseup() {
$document.off('mousemove', mousemove);
$document.off('mouseup', mouseup);
}
}
};
}]);
+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
+4 -2
View File
@@ -92,8 +92,10 @@ means that it should be stateless and idempotent. Angular relies on these proper
the filter only when the inputs to the function change.
<div class="alert alert-warning">
**Note:** filter names must be valid angular expression identifiers, such as `uppercase` or `orderBy`.
Names with special characters, such as hyphens and dots, are not allowed.
**Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
(`myapp_subsection_filterx`).
</div>
The following sample filter reverses a text string. In addition, it conditionally makes the
+16 -13
View File
@@ -3,17 +3,20 @@
@sortOrder 290
@description
# Forms
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
## Simple form
The key directive in understanding two-way data-binding is {@link ng.directive:ngModel ngModel}.
The `ngModel` directive provides the two-way data-binding by synchronizing the model to the view,
as well as view to the model. In addition it provides an {@link ngModel.NgModelController API}
@@ -61,7 +64,7 @@ For example: inputs of type `email` must have a value in the form of `user@domai
# Using CSS classes
## Using CSS classes
To allow styling of form as well as controls, `ngModel` adds these CSS classes:
@@ -125,7 +128,7 @@ and failing to satisfy its validity.
# Binding to form and control state
## Binding to form and control state
A form is an instance of {@link form.FormController FormController}.
The form instance can optionally be published into the scope using the `name` attribute.
@@ -207,7 +210,7 @@ didn't interact with a control
# Custom model update triggers
## Custom model update triggers
By default, any change to the content will trigger a model update and form validation. You can
override this behavior using the {@link ng.directive:ngModelOptions ngModelOptions} directive to
@@ -248,7 +251,7 @@ will update the model only when the control loses focus (blur event).
# Non-immediate (debounced) model updates
## Non-immediate (debounced) model updates
You can delay the model update/validation by using the `debounce` key with the
{@link ng.directive:ngModelOptions ngModelOptions} directive. This delay will also apply to
@@ -289,7 +292,7 @@ after last change.
</file>
</example>
# Custom Validation
## Custom Validation
Angular provides basic implementation for most common HTML5 {@link ng.directive:input input}
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url},
@@ -406,7 +409,7 @@ In the following example we create two directives:
</file>
</example>
# Modifying built-in validators
## Modifying built-in validators
Since Angular itself uses `$validators`, you can easily replace or remove built-in validators,
should you find it necessary. The following example shows you how to overwrite the email validator
@@ -451,7 +454,7 @@ Note that you can alternatively use `ng-pattern` to further restrict the validat
</example>
# Implementing custom form controls (using `ngModel`)
## Implementing custom form controls (using `ngModel`)
Angular implements all of the basic HTML form controls ({@link ng.directive:input input},
{@link ng.directive:select select}, {@link ng.directive:textarea textarea}),
which should be sufficient for most cases. However, if you need more flexibility,
+112
View File
@@ -137,3 +137,115 @@ The Angular datetime filter uses the time zone settings of the browser. The same
application will show different time information depending on the time zone settings of the
computer that the application is running on. Neither JavaScript nor Angular currently supports
displaying the date with a timezone specified by the developer.
<a name="MessageFormat"></a>
## MessageFormat extensions
AngularJS interpolations via `$interpolate` and in templates
support an extended syntax based on a subset of the ICU
MessageFormat that covers plurals and gender selections.
Please refer to our [design doc](https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit)
for a lot more details. You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview).
You can read more about the ICU MessageFormat syntax at
[Formatting Messages | ICU User Guide](http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat).
This extended syntax is provided by way of the
`ngMessageFormat` module that your application can depend
upon (shipped separately as `angular-messageFormat.min.js` and
`angular-messageFormat.js`.) A current limitation of the
`ngMessageFormat` module, is that it does not support
redefining the `$interpolate` start and end symbols. Only the
default `{{` and `}}` are allowed.
This syntax extension, while based on MessageFormat, has
been designed to be backwards compatible with existing
AngularJS interpolation expressions. The key rule is simply
this: **All interpolations are done inside double curlies.**
The top level comma operator after an expression inside the
double curlies causes MessageFormat extensions to be
recognized. Such a top level comma is otherwise illegal in
an Angular expression and is used by MessageFormat to
specify the function (such as plural/select) and it's
related syntax.
To understand the extension, take a look at the ICU
MessageFormat syntax as specified by the ICU documentation.
Anywhere in that MessageFormat that you have regular message
text and you want to substitute an expression, just put it
in double curlies instead of single curlies that
MessageFormat dictates. This has a huge advantage. **You
are no longer limited to simple identifiers for
substitutions**. Because you are using double curlies, you
can stick in any arbitrary interpolation syntax there,
including nesting more MessageFormat expressions! Some
examples will make this clear. In the following example, I
will only be showing you the AngularJS syntax.
### Simple plural example
```
{{numMessages, plural,
=0 { You have no new messages }
=1 { You have one new message }
other { You have # new messages }
}}
```
While I won't be teaching you MessageFormat here, you will
note that the `#` symbol works as expected. You could have
also written it as:
```
{{numMessages, plural,
=0 { You have no new messages }
=1 { You have one new message }
other { You have {{numMessages}} new messages }
}}
```
where you explicitly typed in `numMessages` for "other"
instead of using `#`. They are nearly the same except if
you're using "offset". Refer to the ICU MessageFormat
documentation to learn about offset.
Please note that **other** is a **required** category (for
both the plural syntax and the select syntax that is shown
later.)
### Simple select (for gender) example
```
{{friendGender, select,
male { Invite him }
female { Invite her }
other { Invite them }
}}
```
### More complex example that combines some of these
This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
```
{{recipients.length, plural, offset:1
=0 {You ({{sender.name}}) gave no gifts}
=1 { {{ recipients[0].gender, select,
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
}}
}
one { {{ recipients[0].gender, select,
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
}}
}
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
}}
```
+1 -1
View File
@@ -245,7 +245,7 @@ of the `$watch` expressions and compares them with the previous value. This dirt
asynchronously. This means that assignment such as `$scope.username="angular"` will not
immediately cause a `$watch` to be notified, instead the `$watch` notification is delayed until
the `$digest` phase. This delay is desirable, since it coalesces multiple model updates into one
`$watch` notification as well as it guarantees that during the `$watch` notification no other
`$watch` notification as well as guarantees that during the `$watch` notification no other
`$watch`es are running. If a `$watch` changes the value of the model, it will force additional
`$digest` cycle.
+2 -2
View File
@@ -82,7 +82,7 @@ on the service.
### Registering Services
Services are registered to modules via the {@link angular.Module Module API}.
Typically you use the {@link angular.module Module#factory} API to register a service:
Typically you use the {@link angular.Module#factory Module factory} API to register a service:
```js
var myModule = angular.module('myModule', []);
@@ -181,7 +181,7 @@ of a real browser alert.
```js
var mock, notify;
beforeEach(module('myServiceModule'));
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
+1 -1
View File
@@ -62,7 +62,7 @@ are available on [the Karma website](http://karma-runner.github.io/0.12/intro/in
### Jasmine
[Jasmine](http://jasmine.github.io/1.3/introduction.html) is a test driven development framework for
[Jasmine](http://jasmine.github.io/1.3/introduction.html) is a behavior driven development framework for
JavaScript that has become the most popular choice for testing Angular applications. Jasmine
provides functions to help with structuring your tests and also making assertions. As your tests
grow, keeping them well structured and documented is vital, and Jasmine helps achieve this.
+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.14:
```
<!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.14/angular.min.js"></script>
</head>
<body>
</body>
+37 -1
View File
@@ -20,6 +20,42 @@ AngularJS is 100% JavaScript, 100% client-side and compatible with both desktop
So it's definitely not a plugin or some other native browser extension.
### What is the AngularJS versioning strategy?
In Angular 1 we do not allow intentional breaking changes to appear in versions where only the "patch"
number changes. For example between 1.3.12 and 1.3.13 there can be no breaking changes. We do allow
breaking changes happen between "minor" number changes. For example between 1.3.15 and 1.4.0 there
will be a number of breaking changes. We also allow breaking changes between beta releases of Angular.
For example between 1.4.0-beta.4 and 1.4.0-beta.5 there may be breaking changes. We try hard to minimize
these kinds of change only to those where there is a strong use case such as a strongly requested feature
improvement, a considerable simplification of the code or a measurable performance improvement.
When adding new code to branches of Angular, have a very stringent commit policy:
- Every commit must contain tests and documentation updates alongside the code changes and that all the
tests must pass;
- Commit messages must be written in a specific manner that allows us to parse them and extract the changes
for release notes.
The Angular code base has a very large set of unit tests (over 4000) and end to end tests, which are pretty
comprehensive. This means that a breaking change will require one or more tests to be changed to allow the
tests to pass. So when a commit includes tests that are being removed or modified, this is a flag that the
code might include a breaking change. When reviewing the commit we can then decide whether there really is
a breaking change and if it is appropriate for the branch to which it is being merged. If so, then we
require that the commit message contains an appropriate breaking change message.
Additionally, when a commit lands in our master repository it is synced to Google where we test it against
over 2000 applications using the test suites of these applications. This allows us to catch regressions
quickly before a release. We've had a pretty good experience with this setup. Only bugs that affect features
not used at Google or without sufficient test coverage, have a chance of making it through.
Lastly, when we are making a release we generate updates to the changelog directly from the commits. This
generated update contains a highlighted section that contains all the breaking changes that have been
extracted from the commits. We can quickly see in the new changelog exactly what commits contain breaking
changes and so can application developers when they are deciding whether to update to a new version of
Angular.
### Is AngularJS a templating system?
At the highest level, Angular does look like just another templating system. But there is one
@@ -67,7 +103,7 @@ illustration, we typically build snappy apps with hundreds or thousands of activ
### 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?
+5 -5
View File
@@ -65,7 +65,7 @@ __`app/index.html`:__
## What is the code doing?
* `ng-app` directive:
**`ng-app` directive:**
<html ng-app>
@@ -77,17 +77,17 @@ __`app/index.html`:__
This gives application developers the freedom to tell Angular if the entire html page or only a
portion of it should be treated as the Angular application.
* AngularJS script tag:
**AngularJS script tag:**
<script src="bower_components/angular/angular.js">
This code downloads the `angular.js` script and registers a callback that will be executed by the
This code downloads the `angular.js` script which registers a callback that will be executed by the
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
looks for the {@link ng.directive:ngApp ngApp} directive. If
Angular finds the directive, it will bootstrap the application with the root of the application DOM
being the element on which the `ngApp` directive was defined.
* Double-curly binding with an expression:
**Double-curly binding with an expression:**
Nothing here {{'yet' + '!'}}
@@ -111,7 +111,7 @@ being the element on which the `ngApp` directive was defined.
## Bootstrapping AngularJS apps
Bootstrapping AngularJS apps automatically using the `ngApp` directive is very easy and suitable
for most cases. In advanced cases, such as when using script loaders, you can use
for most cases. In advanced cases, such as when using script loaders, you can use the
{@link guide/bootstrap imperative / manual way} to bootstrap the app.
There are 3 important things that happen during the app bootstrap:
+2 -2
View File
@@ -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.
+4
View File
@@ -184,7 +184,11 @@ describe("extractDateTimeSymbols", function() {
'nov.', 'déc.'],
DAY: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
SHORTDAY: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
FIRSTDAYOFWEEK: 6,
WEEKENDRANGE: [5, 6],
AMPMS: ['AM', 'PM'],
ERAS: ['av. J.-C.', 'ap. J.-C.'],
ERANAMES: ['avant Jésus-Christ', 'après Jésus-Christ'],
medium: 'yyyy-MM-dd HH:mm:ss',
short: 'yy-MM-dd HH:mm',
fullDate: 'EEEE d MMMM y',
+4
View File
@@ -42,6 +42,10 @@ function convertDatetimeData(dataObj) {
datetimeFormats.DAY = dataObj.WEEKDAYS;
datetimeFormats.SHORTDAY = dataObj.SHORTWEEKDAYS;
datetimeFormats.AMPMS = dataObj.AMPMS;
datetimeFormats.FIRSTDAYOFWEEK = dataObj.FIRSTDAYOFWEEK;
datetimeFormats.WEEKENDRANGE = dataObj.WEEKENDRANGE;
datetimeFormats.ERAS = dataObj.ERAS;
datetimeFormats.ERANAMES = dataObj.ERANAMES;
datetimeFormats.medium = dataObj.DATEFORMATS[2] + ' ' + dataObj.TIMEFORMATS[2];
+12
View File
@@ -66,6 +66,12 @@ module.exports = function(config, specificOptions) {
platform: 'Windows 8.1',
version: '11'
},
'SL_iOS': {
base: "SauceLabs",
browserName: "iphone",
platform: "OS X 10.10",
version: "8.1"
},
'BS_Chrome': {
base: 'BrowserStack',
@@ -105,6 +111,12 @@ module.exports = function(config, specificOptions) {
browser_version: '11.0',
os: 'Windows',
os_version: '8.1'
},
'BS_iOS': {
base: 'BrowserStack',
device: 'iPhone 6',
os: 'ios',
os_version: '8.0'
}
}
});
+3 -8
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');
@@ -192,6 +185,8 @@ module.exports = {
var mapFileName = mapFile.match(/[^\/]+$/)[0];
var errorFileName = file.replace(/\.js$/, '-errors.json');
var versionNumber = grunt.config('NG_VERSION').full;
var compilationLevel = (file === 'build/angular-messageFormat.js') ?
'ADVANCED_OPTIMIZATIONS' : 'SIMPLE_OPTIMIZATIONS';
shell.exec(
'java ' +
this.java32flags() + ' ' +
@@ -199,7 +194,7 @@ module.exports = {
'-cp bower_components/closure-compiler/compiler.jar' + classPathSep +
'bower_components/ng-closure-runner/ngcompiler.jar ' +
'org.angularjs.closurerunner.NgClosureRunner ' +
'--compilation_level SIMPLE_OPTIMIZATIONS ' +
'--compilation_level ' + compilationLevel + ' ' +
'--language_in ECMASCRIPT5_STRICT ' +
'--minerr_pass ' +
'--minerr_errors ' + errorFileName + ' ' +
+3 -3
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.7-linux.tar.gz"
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
CONNECT_DOWNLOAD="sc-4.3-linux.tar.gz"
CONNECT_DOWNLOAD="sc-4.3.7-linux.tar.gz"
CONNECT_LOG="$LOGS_DIR/sauce-connect"
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
@@ -46,5 +46,5 @@ echo "Starting Sauce Connect in the background, logging into:"
echo " $CONNECT_LOG"
echo " $CONNECT_STDOUT"
echo " $CONNECT_STDERR"
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS -v \
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS \
--logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT &
+7 -1
View File
@@ -110,10 +110,16 @@ var getPreviousVersions = function() {
})
.filter()
.map(function(version) {
// angular.js didn't follow semantic version until 1.20rc1
if ((version.major === 1 && version.minor === 0 && version.prerelease.length > 0) || (version.major === 1 && version.minor === 2 && version.prerelease[0] === 'rc1')) {
version.version = [version.major, version.minor, version.patch].join('.') + version.prerelease.join('');
version.raw = 'v' + version.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.patch < 2)) {
version.docsUrl += '-' + version.version;
version.isOldDocsUrl = true;
}
return version;
})
File diff suppressed because it is too large Load Diff
+6954 -1523
View File
File diff suppressed because it is too large Load Diff
+7 -2
View File
@@ -6,6 +6,11 @@
"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",
@@ -39,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.32",
"karma-browserstack-launcher": "0.1.2",
"karma-chrome-launcher": "0.1.5",
"karma-firefox-launcher": "0.1.3",
@@ -53,7 +58,7 @@
"marked": "~0.3.0",
"node-html-encoder": "0.0.2",
"promises-aplus-tests": "~2.1.0",
"protractor": "^1.6.0",
"protractor": "^2.0.0",
"q": "~1.0.0",
"q-io": "^1.10.9",
"qq": "^0.3.5",
+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
+17 -25
View File
@@ -23,22 +23,26 @@ function init {
}
function prepare {
if [[ $IS_SNAPSHOT_BUILD ]]; then
# nothing to prepare for snapshot builds as
# code.angularjs.org will fetch the current snapshot from
# the build server during publish
exit 0
fi
echo "-- Cloning code.angularjs.org"
git clone git@github.com:angular/code.angularjs.org.git $REPO_DIR
git clone git@github.com:angular/code.angularjs.org.git $REPO_DIR --depth=1
#
# copy the files from the build
#
echo "-- Updating code.angularjs.org"
mkdir $REPO_DIR/$NEW_VERSION
cp -r $BUILD_DIR/* $REPO_DIR/$NEW_VERSION/
if [[ $IS_SNAPSHOT_BUILD ]]; then
#
# update the snapshot folder
#
rm -rf $REPO_DIR/snapshot
mkdir $REPO_DIR/snapshot
cp -r $BUILD_DIR/* $REPO_DIR/snapshot/
else
#
# copy the files from the build
#
mkdir $REPO_DIR/$NEW_VERSION
cp -r $BUILD_DIR/* $REPO_DIR/$NEW_VERSION/
fi
#
# commit
@@ -50,13 +54,6 @@ function prepare {
}
function _update_snapshot() {
for backend in "$@" ; do
echo "-- Updating snapshot version: backend=$backend"
curl -G --data-urlencode "ver=$NEW_VERSION" http://$backend:8003/fetchLatestSnapshot.php
done
}
function _update_code() {
cd $REPO_DIR
@@ -74,12 +71,7 @@ function publish {
# the currently serving Compute Engine backends.
# code.angularjs.org is served out of port 8003 on these backends.
backends=("$(dig backends.angularjs.org +short TXT | python -c 'print raw_input()[1:-1].replace(",", "\n")')")
if [[ $IS_SNAPSHOT_BUILD ]]; then
_update_snapshot ${backends[@]}
else
_update_code ${backends[@]}
fi
_update_code ${backends[@]}
}
source $(dirname $0)/../utils.inc
@@ -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
+2 -2
View File
@@ -7,9 +7,9 @@ export SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
if [ $JOB = "unit" ]; then
if [ "$BROWSER_PROVIDER" == "browserstack" ]; then
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11"
BROWSERS="BS_Chrome,BS_Safari,BS_Firefox,BS_IE_9,BS_IE_10,BS_IE_11,BS_iOS"
else
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11"
BROWSERS="SL_Chrome,SL_Safari,SL_Firefox,SL_IE_9,SL_IE_10,SL_IE_11,SL_iOS"
fi
grunt test:promises-aplus
@@ -2,6 +2,18 @@
# Wait for Connect to be ready before exiting
# Time out if we wait for more than 2 minutes, so that we can print logs.
let "counter=0"
while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do
let "counter++"
if [ $counter -gt 240 ]; then
echo "Timed out after 2 minutes waiting for browser provider ready file"
# We must manually print logs here because travis will not run
# after_script commands if the failure occurs before the script
# phase.
./scripts/travis/print_logs.sh
exit 5
fi
sleep .5
done
+4
View File
@@ -35,6 +35,7 @@
"extend": false,
"toInt": false,
"inherit": false,
"merge": false,
"noop": false,
"identity": false,
"valueFn": false,
@@ -70,6 +71,8 @@
"toJsonReplacer": false,
"toJson": false,
"fromJson": false,
"convertTimezoneToLocal": false,
"timezoneToOffset": false,
"startingTag": false,
"tryDecodeURIComponent": false,
"parseKeyValue": false,
@@ -93,6 +96,7 @@
"skipDestroyOnNextJQueryCleanData": true,
"NODE_TYPE_ELEMENT": false,
"NODE_TYPE_ATTRIBUTE": false,
"NODE_TYPE_TEXT": false,
"NODE_TYPE_COMMENT": false,
"NODE_TYPE_COMMENT": false,
+150 -24
View File
@@ -29,6 +29,7 @@
extend: true,
toInt: true,
inherit: true,
merge: true,
noop: true,
identity: true,
valueFn: true,
@@ -58,12 +59,15 @@
shallowCopy: true,
equals: true,
csp: true,
jq: true,
concat: true,
sliceArgs: true,
bind: true,
toJsonReplacer: true,
toJson: true,
fromJson: true,
convertTimezoneToLocal: true,
timezoneToOffset: true,
startingTag: true,
tryDecodeURIComponent: true,
parseKeyValue: true,
@@ -84,6 +88,7 @@
createMap: true,
NODE_TYPE_ELEMENT: true,
NODE_TYPE_ATTRIBUTE: true,
NODE_TYPE_TEXT: true,
NODE_TYPE_COMMENT: true,
NODE_TYPE_DOCUMENT: true,
@@ -317,6 +322,31 @@ function setHashKey(obj, h) {
}
}
function baseExtend(dst, objs, deep) {
var h = dst.$$hashKey;
for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
if (!isObject(obj) && !isFunction(obj)) continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];
if (deep && isObject(src)) {
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
baseExtend(dst[key], [src], true);
} else {
dst[key] = src;
}
}
}
setHashKey(dst, h);
return dst;
}
/**
* @ngdoc function
* @name angular.extend
@@ -327,30 +357,43 @@ function setHashKey(obj, h) {
* Extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
* by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`.
* Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy).
*
* **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use
* {@link angular.merge} for this.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function extend(dst) {
var h = dst.$$hashKey;
for (var i = 1, ii = arguments.length; i < ii; i++) {
var obj = arguments[i];
if (obj) {
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
dst[key] = obj[key];
}
}
}
setHashKey(dst, h);
return dst;
return baseExtend(dst, slice.call(arguments, 1), false);
}
/**
* @ngdoc function
* @name angular.merge
* @module ng
* @kind function
*
* @description
* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so
* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`.
*
* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source
* objects, performing a deep copy.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
* @returns {Object} Reference to `dst`.
*/
function merge(dst) {
return baseExtend(dst, slice.call(arguments, 1), true);
}
function toInt(str) {
return parseInt(str, 10);
}
@@ -477,6 +520,12 @@ function isString(value) {return typeof value === 'string';}
* @description
* Determines if a reference is a `Number`.
*
* This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
*
* If you wish to exclude these then you can use the native
* [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
* method.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
@@ -859,10 +908,11 @@ function equals(o1, o2) {
} else if (isDate(o1)) {
if (!isDate(o2)) return false;
return equals(o1.getTime(), o2.getTime());
} else if (isRegExp(o1) && isRegExp(o2)) {
return o1.toString() == o2.toString();
} else if (isRegExp(o1)) {
return isRegExp(o2) ? o1.toString() == o2.toString() : false;
} else {
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
keySet = {};
for (key in o1) {
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
@@ -901,7 +951,58 @@ 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, prefix, name;
for (i = 0; i < ii; ++i) {
prefix = ngAttrPrefixes[i];
if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
name = el.getAttribute(prefix + 'jq');
break;
}
}
return (jq.name_ = name);
};
function concat(array1, array2, index) {
return array1.concat(slice.call(array2, index));
@@ -980,8 +1081,8 @@ function toJsonReplacer(key, value) {
* stripped since angular uses this notation internally.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
* @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
* If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
* @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
* If set to an integer, the JSON output will contain that many spaces per indentation.
* @returns {string|undefined} JSON-ified string representing `obj`.
*/
function toJson(obj, pretty) {
@@ -1012,6 +1113,26 @@ function fromJson(json) {
}
function timezoneToOffset(timezone, fallback) {
var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
}
function addDateMinutes(date, minutes) {
date = new Date(date.getTime());
date.setMinutes(date.getMinutes() + minutes);
return date;
}
function convertTimezoneToLocal(date, timezone, reverse) {
reverse = reverse ? -1 : 1;
var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
}
/**
* @returns {string} Returns the string representation of the element.
*/
@@ -1140,10 +1261,9 @@ var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
function getNgAttribute(element, ngAttr) {
var attr, i, ii = ngAttrPrefixes.length;
element = jqLite(element);
for (i = 0; i < ii; ++i) {
attr = ngAttrPrefixes[i] + ngAttr;
if (isString(attr = element.attr(attr))) {
if (isString(attr = element.getAttribute(attr))) {
return attr;
}
}
@@ -1474,7 +1594,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
@@ -1613,6 +1738,7 @@ function createMap() {
}
var NODE_TYPE_ELEMENT = 1;
var NODE_TYPE_ATTRIBUTE = 2;
var NODE_TYPE_TEXT = 3;
var NODE_TYPE_COMMENT = 8;
var NODE_TYPE_DOCUMENT = 9;
+15 -2
View File
@@ -57,6 +57,8 @@
$AnchorScrollProvider,
$AnimateProvider,
$$CoreAnimateQueueProvider,
$$CoreAnimateRunnerProvider,
$BrowserProvider,
$CacheFactoryProvider,
$ControllerProvider,
@@ -65,7 +67,10 @@
$FilterProvider,
$InterpolateProvider,
$IntervalProvider,
$$HashMapProvider,
$HttpProvider,
$HttpParamSerializerProvider,
$HttpParamSerializerJQLikeProvider,
$HttpBackendProvider,
$LocationProvider,
$LogProvider,
@@ -84,7 +89,8 @@
$$RAFProvider,
$$AsyncCallbackProvider,
$WindowProvider,
$$jqLiteProvider
$$jqLiteProvider,
$$CookieReaderProvider
*/
@@ -116,6 +122,7 @@ function publishExternalAPI(angular) {
'bootstrap': bootstrap,
'copy': copy,
'extend': extend,
'merge': merge,
'equals': equals,
'element': jqLite,
'forEach': forEach,
@@ -212,6 +219,8 @@ function publishExternalAPI(angular) {
$provide.provider({
$anchorScroll: $AnchorScrollProvider,
$animate: $AnimateProvider,
$$animateQueue: $$CoreAnimateQueueProvider,
$$AnimateRunner: $$CoreAnimateRunnerProvider,
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
@@ -221,6 +230,8 @@ function publishExternalAPI(angular) {
$interpolate: $InterpolateProvider,
$interval: $IntervalProvider,
$http: $HttpProvider,
$httpParamSerializer: $HttpParamSerializerProvider,
$httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider,
$httpBackend: $HttpBackendProvider,
$location: $LocationProvider,
$log: $LogProvider,
@@ -238,7 +249,9 @@ function publishExternalAPI(angular) {
$window: $WindowProvider,
$$rAF: $$RAFProvider,
$$asyncCallback: $$AsyncCallbackProvider,
$$jqLite: $$jqLiteProvider
$$jqLite: $$jqLiteProvider,
$$HashMap: $$HashMapProvider,
$$cookieReader: $$CookieReaderProvider
});
}
]);
+6
View File
@@ -73,3 +73,9 @@ HashMap.prototype = {
return value;
}
};
var $$HashMapProvider = [function() {
this.$get = [function() {
return HashMap;
}];
}];
+15
View File
@@ -1,5 +1,16 @@
'use strict';
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind. *
* Changes to this file can potentially create security vulnerabilities. *
* An approval from 2 Core members with history of modifying *
* this file is required. *
* *
* Does the change somehow allow for arbitrary javascript to be executed? *
* Or allows for someone to change the prototype of built-in objects? *
* Or gives undesired access to variables likes document or window? *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* global JQLitePrototype: true,
addEventListenerFn: true,
removeEventListenerFn: true,
@@ -587,6 +598,10 @@ forEach({
},
attr: function(element, name, value) {
var nodeType = element.nodeType;
if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
return;
}
var lowercasedName = lowercase(name);
if (BOOLEAN_ATTR[lowercasedName]) {
if (isDefined(value)) {
+21 -2
View File
@@ -193,6 +193,18 @@ function setupModuleLoader(window) {
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
/**
* @ngdoc method
* @name angular.Module#decorator
* @module ng
* @param {string} The name of the service to decorate.
* @param {Function} This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance.
* @description
* See {@link auto.$provide#decorator $provide.decorator()}.
*/
decorator: invokeLater('$provide', 'decorator'),
/**
* @ngdoc method
* @name angular.Module#animation
@@ -206,7 +218,7 @@ function setupModuleLoader(window) {
*
*
* Defines an animation hook that can be later used with
* {@link ngAnimate.$animate $animate} service and directives that use this service.
* {@link $animate $animate} service and directives that use this service.
*
* ```js
* module.animation('.animation-name', function($inject1, $inject2) {
@@ -231,10 +243,17 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#filter
* @module ng
* @param {string} name Filter name.
* @param {string} name Filter name - this must be a valid angular expression identifier
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
*
* <div class="alert alert-warning">
* **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
* Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
* your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
* (`myapp_subsection_filterx`).
* </div>
*/
filter: invokeLater('$filterProvider', 'register'),
+10 -5
View File
@@ -38,9 +38,10 @@ function $AnchorScrollProvider() {
* @requires $rootScope
*
* @description
* When called, it checks the current value of {@link ng.$location#hash $location.hash()} and
* scrolls to the related element, according to the rules specified in the
* [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
* When called, it scrolls to the element related to the specified `hash` or (if omitted) to the
* current value of {@link ng.$location#hash $location.hash()}, according to the rules specified
* in the
* [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
*
* It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to
* match any anchor whenever it changes. This can be disabled by calling
@@ -49,6 +50,9 @@ function $AnchorScrollProvider() {
* Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a
* vertical scroll-offset (either fixed or dynamic).
*
* @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of
* {@link ng.$location#hash $location.hash()} will be used.
*
* @property {(number|function|jqLite)} yOffset
* If set, specifies a vertical scroll-offset. This is often useful when there are fixed
* positioned elements at the top of the page, such as navbars, headers etc.
@@ -232,8 +236,9 @@ function $AnchorScrollProvider() {
}
}
function scroll() {
var hash = $location.hash(), elm;
function scroll(hash) {
hash = isString(hash) ? hash : $location.hash();
var elm;
// empty hash, scroll to the top of the page
if (!hash) scrollTo(null);
+329 -246
View File
@@ -1,6 +1,151 @@
'use strict';
var $animateMinErr = minErr('$animate');
var ELEMENT_NODE = 1;
function mergeClasses(a,b) {
if (!a && !b) return '';
if (!a) return b;
if (!b) return a;
if (isArray(a)) a = a.join(' ');
if (isArray(b)) b = b.join(' ');
return a + ' ' + b;
}
function extractElementNode(element) {
for (var i = 0; i < element.length; i++) {
var elm = element[i];
if (elm.nodeType === ELEMENT_NODE) {
return elm;
}
}
}
function splitClasses(classes) {
if (isString(classes)) {
classes = classes.split(' ');
}
var obj = {};
forEach(classes, function(klass) {
// sometimes the split leaves empty string values
// incase extra spaces were applied to the options
if (klass.length) {
obj[klass] = true;
}
});
return obj;
}
var $$CoreAnimateRunnerProvider = function() {
this.$get = ['$q', '$$rAF', function($q, $$rAF) {
function AnimateRunner() {}
AnimateRunner.all = noop;
AnimateRunner.chain = noop;
AnimateRunner.prototype = {
end: noop,
cancel: noop,
resume: noop,
pause: noop,
complete: noop,
then: function(pass, fail) {
return $q(function(resolve) {
$$rAF(function() {
resolve();
});
}).then(pass, fail);
}
};
return AnimateRunner;
}];
};
// this is prefixed with Core since it conflicts with
// the animateQueueProvider defined in ngAnimate/animateQueue.js
var $$CoreAnimateQueueProvider = function() {
var postDigestQueue = new HashMap();
var postDigestElements = [];
this.$get = ['$$AnimateRunner', '$rootScope',
function($$AnimateRunner, $rootScope) {
return {
enabled: noop,
on: noop,
off: noop,
push: function(element, event, options, domOperation) {
domOperation && domOperation();
options = options || {};
options.from && element.css(options.from);
options.to && element.css(options.to);
if (options.addClass || options.removeClass) {
addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
}
return new $$AnimateRunner(); // jshint ignore:line
}
};
function addRemoveClassesPostDigest(element, add, remove) {
var data = postDigestQueue.get(element);
var classVal;
if (!data) {
postDigestQueue.put(element, data = {});
postDigestElements.push(element);
}
if (add) {
forEach(add.split(' '), function(className) {
if (className) {
data[className] = true;
}
});
}
if (remove) {
forEach(remove.split(' '), function(className) {
if (className) {
data[className] = false;
}
});
}
if (postDigestElements.length > 1) return;
$rootScope.$$postDigest(function() {
forEach(postDigestElements, function(element) {
var data = postDigestQueue.get(element);
if (data) {
var existing = splitClasses(element.attr('class'));
var toAdd = '';
var toRemove = '';
forEach(data, function(status, className) {
var hasClass = !!existing[className];
if (status !== hasClass) {
if (status) {
toAdd += (toAdd.length ? ' ' : '') + className;
} else {
toRemove += (toRemove.length ? ' ' : '') + className;
}
}
});
forEach(element, function(elm) {
toAdd && jqLiteAddClass(elm, toAdd);
toRemove && jqLiteRemoveClass(elm, toRemove);
});
postDigestQueue.remove(element);
}
});
postDigestElements.length = 0;
});
}
}];
};
/**
* @ngdoc provider
@@ -8,20 +153,18 @@ var $animateMinErr = minErr('$animate');
*
* @description
* Default implementation of $animate that doesn't perform any animations, instead just
* synchronously performs DOM
* updates and calls done() callbacks.
* synchronously performs DOM updates and resolves the returned runner promise.
*
* In order to enable animations the ngAnimate module has to be loaded.
* In order to enable animations the `ngAnimate` module has to be loaded.
*
* To see the functional implementation check out src/ngAnimate/animate.js
* To see the functional implementation check out `src/ngAnimate/animate.js`.
*/
var $AnimateProvider = ['$provide', function($provide) {
var provider = this;
this.$$registeredAnimations = [];
this.$$selectors = {};
/**
/**
* @ngdoc method
* @name $animateProvider#register
*
@@ -30,33 +173,43 @@ var $AnimateProvider = ['$provide', function($provide) {
* animation object which contains callback functions for each event that is expected to be
* animated.
*
* * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction`
* must be called once the element animation is complete. If a function is returned then the
* animation service will use this function to cancel the animation whenever a cancel event is
* triggered.
* * `eventFn`: `function(element, ... , doneFunction, options)`
* The element to animate, the `doneFunction` and the options fed into the animation. Depending
* on the type of animation additional arguments will be injected into the animation function. The
* list below explains the function signatures for the different animation methods:
*
* - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
* - addClass: function(element, addedClasses, doneFunction, options)
* - removeClass: function(element, removedClasses, doneFunction, options)
* - enter, leave, move: function(element, doneFunction, options)
* - animate: function(element, fromStyles, toStyles, doneFunction, options)
*
* Make sure to trigger the `doneFunction` once the animation is fully complete.
*
* ```js
* return {
* eventFn : function(element, done) {
* //code to run the animation
* //once complete, then run done()
* return function cancellationFunction() {
* //code to cancel the animation
* }
* }
* }
* //enter, leave, move signature
* eventFn : function(element, done, options) {
* //code to run the animation
* //once complete, then run done()
* return function endFunction(wasCancelled) {
* //code to cancel the animation
* }
* }
* }
* ```
*
* @param {string} name The name of the animation.
* @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
* @param {Function} factory The factory function that will be executed to return the animation
* object.
*/
this.register = function(name, factory) {
if (name && name.charAt(0) !== '.') {
throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name);
}
var key = name + '-animation';
if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel',
"Expecting class selector starting with '.' got '{0}'.", name);
this.$$selectors[name.substr(1)] = key;
provider.$$registeredAnimations[name.substr(1)] = key;
$provide.factory(key, factory);
};
@@ -67,8 +220,8 @@ var $AnimateProvider = ['$provide', function($provide) {
* @description
* Sets and/or returns the CSS class regular expression that is checked when performing
* an animation. Upon bootstrap the classNameFilter value is not set at all and will
* therefore enable $animate to attempt to perform an animation on any element.
* When setting the classNameFilter value, animations will only be performed on elements
* therefore enable $animate to attempt to perform an animation on any element that is triggered.
* When setting the `classNameFilter` value, animations will only be performed on elements
* that successfully match the filter expression. This in turn can boost performance
* for low-powered devices as well as applications containing a lot of structural operations.
* @param {RegExp=} expression The className expression which will be checked against all animations
@@ -81,98 +234,48 @@ var $AnimateProvider = ['$provide', function($provide) {
return this.$$classNameFilter;
};
this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) {
var currentDefer;
function runAnimationPostDigest(fn) {
var cancelFn, defer = $$q.defer();
defer.promise.$$cancelFn = function ngAnimateMaybeCancel() {
cancelFn && cancelFn();
};
$rootScope.$$postDigest(function ngAnimatePostDigest() {
cancelFn = fn(function ngAnimateNotifyComplete() {
defer.resolve();
});
});
return defer.promise;
}
function resolveElementClasses(element, classes) {
var toAdd = [], toRemove = [];
var hasClasses = createMap();
forEach((element.attr('class') || '').split(/\s+/), function(className) {
hasClasses[className] = true;
});
forEach(classes, function(status, className) {
var hasClass = hasClasses[className];
// If the most recent class manipulation (via $animate) was to remove the class, and the
// element currently has the class, the class is scheduled for removal. Otherwise, if
// the most recent class manipulation (via $animate) was to add the class, and the
// element does not currently have the class, the class is scheduled to be added.
if (status === false && hasClass) {
toRemove.push(className);
} else if (status === true && !hasClass) {
toAdd.push(className);
this.$get = ['$$animateQueue', function($$animateQueue) {
function domInsert(element, parentElement, afterElement) {
// if for some reason the previous element was removed
// from the dom sometime before this code runs then let's
// just stick to using the parent element as the anchor
if (afterElement) {
var afterNode = extractElementNode(afterElement);
if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
afterElement = null;
}
});
return (toAdd.length + toRemove.length) > 0 &&
[toAdd.length ? toAdd : null, toRemove.length ? toRemove : null];
}
function cachedClassManipulation(cache, classes, op) {
for (var i=0, ii = classes.length; i < ii; ++i) {
var className = classes[i];
cache[className] = op;
}
}
function asyncPromise() {
// only serve one instance of a promise in order to save CPU cycles
if (!currentDefer) {
currentDefer = $$q.defer();
$$asyncCallback(function() {
currentDefer.resolve();
currentDefer = null;
});
}
return currentDefer.promise;
}
function applyStyles(element, options) {
if (angular.isObject(options)) {
var styles = extend(options.from || {}, options.to || {});
element.css(styles);
}
afterElement ? afterElement.after(element) : parentElement.prepend(element);
}
/**
*
* @ngdoc service
* @name $animate
* @description The $animate service provides rudimentary DOM manipulation functions to
* insert, remove and move elements within the DOM, as well as adding and removing classes.
* This service is the core service used by the ngAnimate $animator service which provides
* high-level animation hooks for CSS and JavaScript.
* @description The $animate service exposes a series of DOM utility methods that provide support
* for animation hooks. The default behavior is the application of DOM operations, however,
* when an animation is detected (and animations are enabled), $animate will do the heavy lifting
* to ensure that animation runs with the triggered DOM operation.
*
* $animate is available in the AngularJS core, however, the ngAnimate module must be included
* to enable full out animation support. Otherwise, $animate will only perform simple DOM
* manipulation operations.
* By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't
* included and only when it is active then the animation hooks that `$animate` triggers will be
* functional. Once active then all structural `ng-` directives will trigger animations as they perform
* their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
* `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
*
* To learn more about enabling animation support, click here to visit the {@link ngAnimate
* ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service
* page}.
* It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
*
* To learn more about enabling animation support, click here to visit the
* {@link ngAnimate ngAnimate module page}.
*/
return {
animate: function(element, from, to) {
applyStyles(element, { from: from, to: to });
return asyncPromise();
// we don't call it directly since non-existant arguments may
// be interpreted as null within the sub enabled function
on: $$animateQueue.on,
off: $$animateQueue.off,
enabled: $$animateQueue.enabled,
cancel: function(runner) {
runner.cancel && runner.end();
},
/**
@@ -180,38 +283,23 @@ var $AnimateProvider = ['$provide', function($provide) {
* @ngdoc method
* @name $animate#enter
* @kind function
* @description Inserts the element into the DOM either after the `after` element or
* as the first child within the `parent` element. When the function is called a promise
* is returned that will be resolved at a later time.
* @description Inserts the element into the DOM either after the `after` element (if provided) or
* as the first child within the `parent` element and then triggers an animation.
* A promise is returned that will be resolved during the next digest once the animation
* has completed.
*
* @param {DOMElement} element the element which will be inserted into the DOM
* @param {DOMElement} parent the parent element which will append the element as
* a child (if the after element is not present)
* @param {DOMElement} after the sibling element which will append the element
* after itself
* @param {object=} options an optional collection of styles that will be applied to the element.
* a child (so long as the after element is not present)
* @param {DOMElement=} after the sibling element after which the element will be appended
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @return {Promise} the animation callback promise
*/
enter: function(element, parent, after, options) {
applyStyles(element, options);
after ? after.after(element)
: parent.prepend(element);
return asyncPromise();
},
/**
*
* @ngdoc method
* @name $animate#leave
* @kind function
* @description Removes the element from the DOM. When the function is called a promise
* is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will be removed from the DOM
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
leave: function(element, options) {
element.remove();
return asyncPromise();
parent = parent || after.parent();
domInsert(element, parent, after);
return $$animateQueue.push(element, 'enter', options);
},
/**
@@ -219,153 +307,148 @@ var $AnimateProvider = ['$provide', function($provide) {
* @ngdoc method
* @name $animate#move
* @kind function
* @description Moves the position of the provided element within the DOM to be placed
* either after the `after` element or inside of the `parent` element. When the function
* is called a promise is returned that will be resolved at a later time.
* @description Inserts (moves) the element into its new position in the DOM either after
* the `after` element (if provided) or as the first child within the `parent` element
* and then triggers an animation. A promise is returned that will be resolved
* during the next digest once the animation has completed.
*
* @param {DOMElement} element the element which will be moved into the new DOM position
* @param {DOMElement} parent the parent element which will append the element as
* a child (so long as the after element is not present)
* @param {DOMElement=} after the sibling element after which the element will be appended
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @param {DOMElement} element the element which will be moved around within the
* DOM
* @param {DOMElement} parent the parent element where the element will be
* inserted into (if the after element is not present)
* @param {DOMElement} after the sibling element where the element will be
* positioned next to
* @param {object=} options an optional collection of options that will be applied to the element.
* @return {Promise} the animation callback promise
*/
move: function(element, parent, after, options) {
// Do not remove element before insert. Removing will cause data associated with the
// element to be dropped. Insert will implicitly do the remove.
return this.enter(element, parent, after, options);
parent = parent || after.parent();
domInsert(element, parent, after);
return $$animateQueue.push(element, 'move', options);
},
/**
* @ngdoc method
* @name $animate#leave
* @kind function
* @description Triggers an animation and then removes the element from the DOM.
* When the function is called a promise is returned that will be resolved during the next
* digest once the animation has completed.
*
* @param {DOMElement} element the element which will be removed from the DOM
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @return {Promise} the animation callback promise
*/
leave: function(element, options) {
return $$animateQueue.push(element, 'leave', options, function() {
element.remove();
});
},
/**
* @ngdoc method
* @name $animate#addClass
* @kind function
* @description Adds the provided className CSS class value to the provided element.
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* added to it
* @param {string} className the CSS class which will be added to the element
* @param {object=} options an optional collection of options that will be applied to the element.
*
* @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
* execution, the addClass operation will only be handled after the next digest and it will not trigger an
* animation if element already contains the CSS class or if the class is removed at a later step.
* Note that class-based animations are treated differently compared to structural animations
* (like enter, move and leave) since the CSS classes may be added/removed at different points
* depending if CSS or JavaScript animations are used.
*
* @param {DOMElement} element the element which the CSS classes will be applied to
* @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @return {Promise} the animation callback promise
*/
addClass: function(element, className, options) {
return this.setClass(element, className, [], options);
},
$$addClassImmediately: function(element, className, options) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function(element) {
jqLiteAddClass(element, className);
});
applyStyles(element, options);
return asyncPromise();
options = options || {};
options.addClass = mergeClasses(options.addclass, className);
return $$animateQueue.push(element, 'addClass', options);
},
/**
*
* @ngdoc method
* @name $animate#removeClass
* @kind function
* @description Removes the provided className CSS class value from the provided element.
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have the className value
* removed from it
* @param {string} className the CSS class which will be removed from the element
* @param {object=} options an optional collection of options that will be applied to the element.
*
* @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
* execution, the removeClass operation will only be handled after the next digest and it will not trigger an
* animation if element does not contain the CSS class or if the class is added at a later step.
* Note that class-based animations are treated differently compared to structural animations
* (like enter, move and leave) since the CSS classes may be added/removed at different points
* depending if CSS or JavaScript animations are used.
*
* @param {DOMElement} element the element which the CSS classes will be applied to
* @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @return {Promise} the animation callback promise
*/
removeClass: function(element, className, options) {
return this.setClass(element, [], className, options);
},
$$removeClassImmediately: function(element, className, options) {
element = jqLite(element);
className = !isString(className)
? (isArray(className) ? className.join(' ') : '')
: className;
forEach(element, function(element) {
jqLiteRemoveClass(element, className);
});
applyStyles(element, options);
return asyncPromise();
options = options || {};
options.removeClass = mergeClasses(options.removeClass, className);
return $$animateQueue.push(element, 'removeClass', options);
},
/**
*
* @ngdoc method
* @name $animate#setClass
* @kind function
* @description Adds and/or removes the given CSS classes to and from the element.
* When the function is called a promise is returned that will be resolved at a later time.
* @param {DOMElement} element the element which will have its CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
* @param {object=} options an optional collection of options that will be applied to the element.
*
* @description Performs both the addition and removal of a CSS classes on an element and (during the process)
* triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
* `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
* passed. Note that class-based animations are treated differently compared to structural animations
* (like enter, move and leave) since the CSS classes may be added/removed at different points
* depending if CSS or JavaScript animations are used.
*
* @param {DOMElement} element the element which the CSS classes will be applied to
* @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
* @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @return {Promise} the animation callback promise
*/
setClass: function(element, add, remove, options) {
var self = this;
var STORAGE_KEY = '$$animateClasses';
var createdCache = false;
element = jqLite(element);
var cache = element.data(STORAGE_KEY);
if (!cache) {
cache = {
classes: {},
options: options
};
createdCache = true;
} else if (options && cache.options) {
cache.options = angular.extend(cache.options || {}, options);
}
var classes = cache.classes;
add = isArray(add) ? add : add.split(' ');
remove = isArray(remove) ? remove : remove.split(' ');
cachedClassManipulation(classes, add, true);
cachedClassManipulation(classes, remove, false);
if (createdCache) {
cache.promise = runAnimationPostDigest(function(done) {
var cache = element.data(STORAGE_KEY);
element.removeData(STORAGE_KEY);
// in the event that the element is removed before postDigest
// is run then the cache will be undefined and there will be
// no need anymore to add or remove and of the element classes
if (cache) {
var classes = resolveElementClasses(element, cache.classes);
if (classes) {
self.$$setClassImmediately(element, classes[0], classes[1], cache.options);
}
}
done();
});
element.data(STORAGE_KEY, cache);
}
return cache.promise;
options = options || {};
options.addClass = mergeClasses(options.addClass, add);
options.removeClass = mergeClasses(options.removeClass, remove);
return $$animateQueue.push(element, 'setClass', options);
},
$$setClassImmediately: function(element, add, remove, options) {
add && this.$$addClassImmediately(element, add);
remove && this.$$removeClassImmediately(element, remove);
applyStyles(element, options);
return asyncPromise();
},
/**
* @ngdoc method
* @name $animate#animate
* @kind function
*
* @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
* If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take
* on the provided styles. For example, if a transition animation is set for the given className then the provided from and
* to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles
* will be given in as function paramters into the `animate` method (or as apart of the `options` parameter).
*
* @param {DOMElement} element the element which the CSS styles will be applied to
* @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
* @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
* @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
* this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
* (Note that if no animation is detected then this value will not be appplied to the element.)
* @param {object=} options an optional collection of options/styles that will be applied to the element
*
* @return {Promise} the animation callback promise
*/
animate: function(element, from, to, className, options) {
options = options || {};
options.from = options.from ? extend(options.from, from) : from;
options.to = options.to ? extend(options.to, to) : to;
enabled: noop,
cancel: noop
className = className || 'ng-inline-animate';
options.tempClasses = mergeClasses(options.tempClasses, className);
return $$animateQueue.push(element, 'animate', options);
}
};
}];
}];
+19 -127
View File
@@ -73,11 +73,6 @@ function Browser(window, document, $log, $sniffer) {
* @param {function()} callback Function that will be called when no outstanding request
*/
self.notifyWhenNoOutstandingRequests = function(callback) {
// force browser to execute all pollFns - this is needed so that cookies and other pollers fire
// at some deterministic time in respect to the test runner's actions. Leaving things up to the
// regular poller would result in flaky tests.
forEach(pollFns, function(pollFn) { pollFn(); });
if (outstandingRequestCount === 0) {
callback();
} else {
@@ -85,44 +80,6 @@ function Browser(window, document, $log, $sniffer) {
}
};
//////////////////////////////////////////////////////////////
// Poll Watcher API
//////////////////////////////////////////////////////////////
var pollFns = [],
pollTimeout;
/**
* @name $browser#addPollFn
*
* @param {function()} fn Poll function to add
*
* @description
* Adds a function to the list of functions that poller periodically executes,
* and starts polling if not started yet.
*
* @returns {function()} the added function
*/
self.addPollFn = function(fn) {
if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
pollFns.push(fn);
return fn;
};
/**
* @param {number} interval How often should browser call poll functions (ms)
* @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
*
* @description
* Configures the poller to run in the specified intervals, using the specified
* setTimeout fn and kicks it off.
*/
function startPoller(interval, setTimeout) {
(function check() {
forEach(pollFns, function(pollFn) { pollFn(); });
pollTimeout = setTimeout(check, interval);
})();
}
//////////////////////////////////////////////////////////////
// URL API
//////////////////////////////////////////////////////////////
@@ -233,11 +190,19 @@ function Browser(window, document, $log, $sniffer) {
fireUrlChange();
}
function getCurrentState() {
try {
return history.state;
} catch (e) {
// MSIE can reportedly throw when there is no state (UNCONFIRMED).
}
}
// This variable should be used *only* inside the cacheState function.
var lastCachedState = null;
function cacheState() {
// This should be the only place in $browser where `history.state` is read.
cachedState = window.history.state;
cachedState = getCurrentState();
cachedState = isUndefined(cachedState) ? null : cachedState;
// Prevent callbacks fo fire twice if both hashchange & popstate were fired.
@@ -299,6 +264,16 @@ function Browser(window, document, $log, $sniffer) {
return callback;
};
/**
* @private
* Remove popstate and hashchange handler from window.
*
* NOTE: this api is intended for use only by $rootScope.
*/
self.$$applicationDestroyed = function() {
jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange);
};
/**
* Checks whether the url has changed outside of Angular.
* Needs to be exported to be able to check for changes that have been done in sync,
@@ -324,89 +299,6 @@ function Browser(window, document, $log, $sniffer) {
return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : '';
};
//////////////////////////////////////////////////////////////
// Cookies API
//////////////////////////////////////////////////////////////
var lastCookies = {};
var lastCookieString = '';
var cookiePath = self.baseHref();
function safeDecodeURIComponent(str) {
try {
return decodeURIComponent(str);
} catch (e) {
return str;
}
}
/**
* @name $browser#cookies
*
* @param {string=} name Cookie name
* @param {string=} value Cookie value
*
* @description
* The cookies method provides a 'private' low level access to browser cookies.
* It is not meant to be used directly, use the $cookie service instead.
*
* The return values vary depending on the arguments that the method was called with as follows:
*
* - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify
* it
* - cookies(name, value) -> set name to value, if value is undefined delete the cookie
* - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that
* way)
*
* @returns {Object} Hash of all cookies (if called without any parameter)
*/
self.cookies = function(name, value) {
var cookieLength, cookieArray, cookie, i, index;
if (name) {
if (value === undefined) {
rawDocument.cookie = encodeURIComponent(name) + "=;path=" + cookiePath +
";expires=Thu, 01 Jan 1970 00:00:00 GMT";
} else {
if (isString(value)) {
cookieLength = (rawDocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) +
';path=' + cookiePath).length + 1;
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
// - 300 cookies
// - 20 cookies per unique domain
// - 4096 bytes per cookie
if (cookieLength > 4096) {
$log.warn("Cookie '" + name +
"' possibly not set or overflowed because it was too large (" +
cookieLength + " > 4096 bytes)!");
}
}
}
} else {
if (rawDocument.cookie !== lastCookieString) {
lastCookieString = rawDocument.cookie;
cookieArray = lastCookieString.split("; ");
lastCookies = {};
for (i = 0; i < cookieArray.length; i++) {
cookie = cookieArray[i];
index = cookie.indexOf('=');
if (index > 0) { //ignore nameless cookies
name = safeDecodeURIComponent(cookie.substring(0, index));
// the first value that is seen for a cookie is the most
// specific one. values for the same cookie name that
// follow are for less specific paths.
if (lastCookies[name] === undefined) {
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
}
}
}
}
return lastCookies;
}
};
/**
* @name $browser#defer
* @param {function()} fn A function, who's execution should be deferred.
+1 -1
View File
@@ -372,7 +372,7 @@ function $CacheFactoryProvider() {
* the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
* element with ng-app attribute), otherwise the template will be ignored.
*
* Adding via the $templateCache service:
* Adding via the `$templateCache` service:
*
* ```js
* var myApp = angular.module('myApp', []);
+104 -82
View File
@@ -1,5 +1,16 @@
'use strict';
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind. *
* Changes to this file can potentially create security vulnerabilities. *
* An approval from 2 Core members with history of modifying *
* this file is required. *
* *
* Does the change somehow allow for arbitrary javascript to be executed? *
* Or allows for someone to change the prototype of built-in objects? *
* Or gives undesired access to variables likes document or window? *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
*
* DOM-related variables:
@@ -65,6 +76,7 @@
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
* controllerAs: 'stringIdentifier',
* bindToController: false,
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile: function compile(tElement, tAttrs, transclude) {
* return {
@@ -211,7 +223,8 @@
* Require another directive and inject its controller as the fourth argument to the linking function. The
* `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
* injected argument will be an array in corresponding order. If no such directive can be
* found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with:
* found, or if the directive does not have a controller, then an error is raised (unless no link function
* is specified, in which case error checking is skipped). The name can be prefixed with:
*
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
@@ -346,7 +359,7 @@
* `templateUrl` declaration or manual compilation inside the compile function.
* </div>
*
* <div class="alert alert-error">
* <div class="alert alert-danger">
* **Note:** The `transclude` function that is passed to the compile function is deprecated, as it
* e.g. does not know about the right outer scope. Please use the transclude function that is passed
* to the link function instead.
@@ -383,9 +396,15 @@
* * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
* between all directive linking functions.
*
* * `controller` - a controller instance - A controller instance if at least one directive on the
* element defines a controller. The controller is shared among all the directives, which allows
* the directives to use the controllers as a communication channel.
* * `controller` - the directive's required controller instance(s) - Instances are shared
* among all directives, which allows the directives to use the controllers as a communication
* channel. The exact value depends on the directive's `require` property:
* * `string`: the controller instance
* * `array`: array of controller instances
* * no controller(s) required: `undefined`
*
* If a required controller cannot be found, and it is optional, the instance is `null`,
* otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
*
* * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
* This is the same as the `$transclude`
@@ -625,7 +644,7 @@
* @param {string|DOMElement} element Element or HTML string to compile into a template function.
* @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED.
*
* <div class="alert alert-error">
* <div class="alert alert-danger">
* **Note:** Passing a `transclude` function to the $compile function is deprecated, as it
* e.g. will not use the right outer scope. Please pass the transclude function as a
* `parentBoundTranscludeFn` to the link function instead.
@@ -778,6 +797,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return bindings;
}
function assertValidDirectiveName(name) {
var letter = name.charAt(0);
if (!letter || letter !== lowercase(letter)) {
throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
}
}
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -796,6 +822,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
this.directive = function registerDirective(name, directiveFactory) {
assertNotHasOwnProperty(name, 'directive');
if (isString(name)) {
assertValidDirectiveName(name);
assertArg(directiveFactory, 'directiveFactory');
if (!hasDirectives.hasOwnProperty(name)) {
hasDirectives[name] = [];
@@ -1614,7 +1641,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var terminalPriority = -Number.MAX_VALUE,
newScopeDirective,
controllerDirectives = previousCompileContext.controllerDirectives,
controllers,
newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective,
templateDirective = previousCompileContext.templateDirective,
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
@@ -1672,7 +1698,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;
@@ -1840,51 +1866,74 @@ 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
};
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) {
@@ -1911,36 +1960,7 @@ 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) {
@@ -1954,14 +1974,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
isolateScope.$$isolateBindings,
newIsolateScopeDirective, isolateScope);
}
if (controllers) {
if (elementControllers) {
// Initialize bindToController bindings for new/isolate scopes
var scopeDirective = newIsolateScopeDirective || newScopeDirective;
var bindings;
var controllerForBindings;
if (scopeDirective && controllers[scopeDirective.name]) {
if (scopeDirective && elementControllers[scopeDirective.name]) {
bindings = scopeDirective.$$bindings.bindToController;
controller = controllers[scopeDirective.name];
controller = elementControllers[scopeDirective.name];
if (controller && controller.identifier && bindings) {
controllerForBindings = controller;
@@ -1970,18 +1990,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
bindings, scopeDirective);
}
}
forEach(controllers, function(controller) {
var result = controller();
if (result !== controller.instance &&
controller === controllerForBindings) {
// Remove and re-install bindToController bindings
thisLinkFn.$$destroyBindings();
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, result,
bindings, scopeDirective);
for (i in elementControllers) {
controller = elementControllers[i];
var controllerResult = controller();
if (controllerResult !== controller.instance) {
controller.instance = controllerResult;
$element.data('$' + directive.name + 'Controller', controllerResult);
if (controller === controllerForBindings) {
// Remove and re-install bindToController bindings
thisLinkFn.$$destroyBindings();
thisLinkFn.$$destroyBindings =
initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective);
}
}
});
controllers = null;
}
}
// PRELINKING
+56
View File
@@ -0,0 +1,56 @@
'use strict';
/**
* @name $$cookieReader
* @requires $document
*
* @description
* This is a private service for reading cookies used by $http and ngCookies
*
* @return {Object} a key/value map of the current cookies
*/
function $$CookieReader($document) {
var rawDocument = $document[0] || {};
var lastCookies = {};
var lastCookieString = '';
function safeDecodeURIComponent(str) {
try {
return decodeURIComponent(str);
} catch (e) {
return str;
}
}
return function() {
var cookieArray, cookie, i, index, name;
var currentCookieString = rawDocument.cookie || '';
if (currentCookieString !== lastCookieString) {
lastCookieString = currentCookieString;
cookieArray = lastCookieString.split('; ');
lastCookies = {};
for (i = 0; i < cookieArray.length; i++) {
cookie = cookieArray[i];
index = cookie.indexOf('=');
if (index > 0) { //ignore nameless cookies
name = safeDecodeURIComponent(cookie.substring(0, index));
// the first value that is seen for a cookie is the most
// specific one. values for the same cookie name that
// follow are for less specific paths.
if (lastCookies[name] === undefined) {
lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
}
}
}
}
return lastCookies;
};
}
$$CookieReader.$inject = ['$document'];
function $$CookieReaderProvider() {
this.$get = $$CookieReader;
}
+13 -9
View File
@@ -159,20 +159,24 @@
*
* @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>
* <!-- See below for an example of ng-disabled being used correctly -->
* <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 +195,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
*/
+14 -14
View File
@@ -316,7 +316,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
*
* # Alias: {@link ng.directive:ngForm `ngForm`}
*
* In Angular forms can be nested. This means that the outer form is valid when all of the child
* In Angular, forms can be nested. This means that the outer form is valid when all of the child
* forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
* Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
* `<form>` but can be nested. This allows you to have nested forms, which is very useful when
@@ -454,10 +454,12 @@ var formDirectiveFactory = function(isNgForm) {
name: 'form',
restrict: isNgForm ? 'EAC' : 'E',
controller: FormController,
compile: function ngFormCompile(formElement) {
compile: function ngFormCompile(formElement, attr) {
// Setup initial state of the control
formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
return {
pre: function ngFormPreLink(scope, formElement, attr, controller) {
// if `action` attr is not present on the form, prevent the default action (submission)
@@ -488,23 +490,21 @@ var formDirectiveFactory = function(isNgForm) {
});
}
var parentFormCtrl = controller.$$parentForm,
alias = controller.$name;
var parentFormCtrl = controller.$$parentForm;
if (alias) {
setter(scope, alias, controller, alias);
attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
if (alias === newValue) return;
setter(scope, alias, undefined, alias);
alias = newValue;
setter(scope, alias, controller, alias);
parentFormCtrl.$$renameControl(controller, alias);
if (nameAttr) {
setter(scope, controller.$name, controller, controller.$name);
attr.$observe(nameAttr, function(newValue) {
if (controller.$name === newValue) return;
setter(scope, controller.$name, undefined, controller.$name);
parentFormCtrl.$$renameControl(controller, newValue);
setter(scope, controller.$name, controller, controller.$name);
});
}
formElement.on('$destroy', function() {
parentFormCtrl.$removeControl(controller);
if (alias) {
setter(scope, alias, undefined, alias);
if (nameAttr) {
setter(scope, attr[nameAttr], undefined, controller.$name);
}
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
});
+11 -8
View File
@@ -587,7 +587,11 @@ var inputType = {
* Text input with number validation and transformation. Sets the `number` validation
* error if not a valid number.
*
* The model must always be a number, otherwise Angular will throw an error.
* <div class="alert alert-warning">
* The model must always be of type `number` otherwise Angular will throw an error.
* Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
* error docs for more information and an example of how to convert your model if necessary.
* </div>
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -1156,8 +1160,8 @@ function createDateInputType(type, regexp, parseDate, format) {
// parser/formatter in the processing chain so that the model
// contains some different data format!
var parsedDate = parseDate(value, previousDate);
if (timezone === 'UTC') {
parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset());
if (timezone) {
parsedDate = convertTimezoneToLocal(parsedDate, timezone);
}
return parsedDate;
}
@@ -1170,9 +1174,8 @@ function createDateInputType(type, regexp, parseDate, format) {
}
if (isValidDate(value)) {
previousDate = value;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
previousDate = new Date(previousDate.getTime() + timezoneOffset);
if (previousDate && timezone) {
previousDate = convertTimezoneToLocal(previousDate, timezone, true);
}
return $filter('date')(value, format, timezone);
} else {
@@ -1250,7 +1253,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
return value;
});
if (attr.min || attr.ngMin) {
if (isDefined(attr.min) || attr.ngMin) {
var minVal;
ctrl.$validators.min = function(value) {
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
@@ -1266,7 +1269,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
});
}
if (attr.max || attr.ngMax) {
if (isDefined(attr.max) || attr.ngMax) {
var maxVal;
ctrl.$validators.max = function(value) {
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
+32 -13
View File
@@ -96,12 +96,15 @@ function classDirective(name, selector) {
}
function arrayClasses(classVal) {
var classes = [];
if (isArray(classVal)) {
return classVal.join(' ').split(' ');
forEach(classVal, function(v) {
classes = classes.concat(arrayClasses(v));
});
return classes;
} else if (isString(classVal)) {
return classVal.split(' ');
} else if (isObject(classVal)) {
var classes = [];
forEach(classVal, function(v, k) {
if (v) {
classes = classes.concat(k.split(' '));
@@ -129,16 +132,18 @@ function classDirective(name, selector) {
* 1. If the expression evaluates to a string, the string should be one or more space-delimited class
* names.
*
* 2. If the expression evaluates to an array, each element of the array should be a string that is
* one or more space-delimited class names.
*
* 3. If the expression evaluates to an object, then for each key-value pair of the
* 2. If the expression evaluates to an object, then for each key-value pair of the
* object with a truthy value the corresponding key is used as a class name.
*
* 3. If the expression evaluates to an array, each element of the array should either be a string as in
* type 1 or an object as in type 2. This means that you can mix strings and objects together in an array
* to give you more control over what CSS classes appear. See the code below for an example of this.
*
*
* The directive won't add duplicate classes if a particular class was already set.
*
* When the expression changes, the previously added classes are removed and only then the
* new classes are added.
* When the expression changes, the previously added classes are removed and only then are the
* new classes added.
*
* @animations
* **add** - happens just before the class is applied to the elements
@@ -167,10 +172,14 @@ function classDirective(name, selector) {
<input ng-model="style1" placeholder="Type: bold, strike or red"><br>
<input ng-model="style2" placeholder="Type: bold, strike or red"><br>
<input ng-model="style3" placeholder="Type: bold, strike or red"><br>
<hr>
<p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p>
<input ng-model="style4" placeholder="Type: bold, strike"><br>
<input type="checkbox" ng-model="warning"> warning (apply "orange" class)
</file>
<file name="style.css">
.strike {
text-decoration: line-through;
text-decoration: line-through;
}
.bold {
font-weight: bold;
@@ -178,6 +187,9 @@ function classDirective(name, selector) {
.red {
color: red;
}
.orange {
color: orange;
}
</file>
<file name="protractor.js" type="protractor">
var ps = element.all(by.css('p'));
@@ -202,11 +214,18 @@ function classDirective(name, selector) {
});
it('array example should have 3 classes', function() {
expect(ps.last().getAttribute('class')).toBe('');
expect(ps.get(2).getAttribute('class')).toBe('');
element(by.model('style1')).sendKeys('bold');
element(by.model('style2')).sendKeys('strike');
element(by.model('style3')).sendKeys('red');
expect(ps.last().getAttribute('class')).toBe('bold strike red');
expect(ps.get(2).getAttribute('class')).toBe('bold strike red');
});
it('array with map example should have 2 classes', function() {
expect(ps.last().getAttribute('class')).toBe('');
element(by.model('style4')).sendKeys('bold');
element(by.model('warning')).click();
expect(ps.last().getAttribute('class')).toBe('bold orange');
});
</file>
</example>
@@ -256,8 +275,8 @@ function classDirective(name, selector) {
The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure.
Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder
any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure
to view the step by step details of {@link ng.$animate#addClass $animate.addClass} and
{@link ng.$animate#removeClass $animate.removeClass}.
to view the step by step details of {@link $animate#addClass $animate.addClass} and
{@link $animate#removeClass $animate.removeClass}.
*/
var ngClassDirective = classDirective('', true);
+1 -1
View File
@@ -9,7 +9,7 @@
* The `ngInit` directive allows you to evaluate an expression in the
* current scope.
*
* <div class="alert alert-error">
* <div class="alert alert-danger">
* The only appropriate use of `ngInit` is for aliasing special properties of
* {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you
* should use {@link guide/controller controllers} rather than `ngInit`
+24 -20
View File
@@ -129,8 +129,8 @@ is set to `true`. The parse error is stored in `ngModel.$error.parse`.
* data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
* collaborate together to achieve the desired result.
*
* Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element
* contents be edited in place by the user. This will not work on older browsers.
* `contenteditable` is an HTML5 attribute, which tells the browser to let the element
* contents be edited in place by the user.
*
* We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
* module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
@@ -244,6 +244,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ngModelGet = parsedNgModel,
ngModelSet = parsedNgModelAssign,
pendingDebounce = null,
parserValid,
ctrl = this;
this.$$setOptions = function(options) {
@@ -516,16 +517,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// the model although neither viewValue nor the model on the scope changed
var modelValue = ctrl.$$rawModelValue;
// Check if the there's a parse error, so we don't unset it accidentially
var parserName = ctrl.$$parserName || 'parse';
var parserValid = ctrl.$error[parserName] ? false : undefined;
var prevValid = ctrl.$valid;
var prevModelValue = ctrl.$modelValue;
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
// If there was no change in validity, don't update the model
// This prevents changing an invalid modelValue to undefined
if (!allowInvalid && prevValid !== allValid) {
@@ -543,12 +540,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
};
this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
this.$$runValidators = function(modelValue, viewValue, doneCallback) {
currentValidationRunId++;
var localValidationRunId = currentValidationRunId;
// check parser error
if (!processParseErrors(parseValid)) {
if (!processParseErrors()) {
validationDone(false);
return;
}
@@ -558,21 +555,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
}
processAsyncValidators();
function processParseErrors(parseValid) {
function processParseErrors() {
var errorKey = ctrl.$$parserName || 'parse';
if (parseValid === undefined) {
if (parserValid === undefined) {
setValidity(errorKey, null);
} else {
setValidity(errorKey, parseValid);
if (!parseValid) {
if (!parserValid) {
forEach(ctrl.$validators, function(v, name) {
setValidity(name, null);
});
forEach(ctrl.$asyncValidators, function(v, name) {
setValidity(name, null);
});
return false;
}
// Set the parse error last, to prevent unsetting it, should a $validators key == parserName
setValidity(errorKey, parserValid);
return parserValid;
}
return true;
}
@@ -667,7 +665,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
this.$$parseAndValidate = function() {
var viewValue = ctrl.$$lastCommittedViewValue;
var modelValue = viewValue;
var parserValid = isUndefined(modelValue) ? undefined : true;
parserValid = isUndefined(modelValue) ? undefined : true;
if (parserValid) {
for (var i = 0; i < ctrl.$parsers.length; i++) {
@@ -693,7 +691,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
// This can happen if e.g. $setViewValue is called from inside a parser
ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
if (!allowInvalid) {
// Note: Don't check ctrl.$valid here, as we could have
// external validators (e.g. calculated on the server),
@@ -812,8 +810,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// if scope model value and ngModel value are out of sync
// TODO(perf): why not move this to the action fn?
if (modelValue !== ctrl.$modelValue) {
if (modelValue !== ctrl.$modelValue &&
// checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
(ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
) {
ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
parserValid = undefined;
var formatters = ctrl.$formatters,
idx = formatters.length;
@@ -826,7 +828,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
ctrl.$render();
ctrl.$$runValidators(undefined, modelValue, viewValue, noop);
ctrl.$$runValidators(modelValue, viewValue, noop);
}
}
@@ -1098,8 +1100,10 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
`ngModel` as getters/setters.
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
* `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
* otherwise the default timezone of the browser will be used.
* `<input type="date">`, `<input type="time">`, ... . 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.
*
* @example
+77 -29
View File
@@ -32,8 +32,9 @@ var ngOptionsMinErr = minErr('ngOptions');
* 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/).
* **Note:** By default, `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/). When using `track by`
* in an `ngOptions` expression, however, deep equality checks will be performed.
* </div>
*
* ## `select` **`as`**
@@ -94,15 +95,20 @@ var ngOptionsMinErr = minErr('ngOptions');
* * `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:
*
@@ -116,6 +122,8 @@ var ngOptionsMinErr = minErr('ngOptions');
* 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
@@ -129,10 +137,10 @@ var ngOptionsMinErr = minErr('ngOptions');
.controller('ExampleController', ['$scope', function($scope) {
$scope.colors = [
{name:'black', shade:'dark'},
{name:'white', shade:'light'},
{name:'white', shade:'light', notAnOption: true},
{name:'red', shade:'dark'},
{name:'blue', shade:'dark'},
{name:'yellow', shade:'light'}
{name:'blue', shade:'dark', notAnOption: true},
{name:'yellow', shade:'light', notAnOption: false}
];
$scope.myColor = $scope.colors[2]; // red
}]);
@@ -141,6 +149,7 @@ var ngOptionsMinErr = minErr('ngOptions');
<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>
@@ -162,6 +171,12 @@ var ngOptionsMinErr = minErr('ngOptions');
<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/>
@@ -186,16 +201,17 @@ var ngOptionsMinErr = minErr('ngOptions');
*/
// jshint maxlen: false
//000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
// //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: array item variable name
// 5: object item key variable name
// 6: object item value variable name
// 7: collection expression
// 8: track by expression
// 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
@@ -215,14 +231,14 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
// Extract the parts from the ngOptions expression
// The variable name for the value of the item in the collection
var valueName = match[4] || match[6];
var valueName = match[5] || match[7];
// The variable name for the key of the item in the collection
var keyName = match[5];
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[8];
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);
@@ -237,7 +253,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
function getHashOfValue(viewValue) { return hashKey(viewValue); };
var displayFn = $parse(match[2] || match[1]);
var groupByFn = $parse(match[3] || '');
var valuesFn = $parse(match[7]);
var disableWhenFn = $parse(match[4] || '');
var valuesFn = $parse(match[8]);
var locals = {};
var getLocals = keyName ? function(value, key) {
@@ -250,14 +267,16 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
};
function Option(selectValue, viewValue, label, group) {
function Option(selectValue, viewValue, label, group, disabled) {
this.selectValue = selectValue;
this.viewValue = viewValue;
this.label = label;
this.group = group;
this.disabled = disabled;
}
return {
trackBy: trackBy,
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
@@ -267,10 +286,20 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
Object.keys(values).forEach(function getWatchable(key) {
var locals = getLocals(values[key], key);
var label = displayFn(scope, locals);
var selectValue = getTrackByValue(values[key], locals);
watchedArray.push(selectValue);
watchedArray.push(label);
// 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;
}),
@@ -296,7 +325,8 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
var selectValue = getTrackByValue(viewValue, locals);
var label = displayFn(scope, locals);
var group = groupByFn(scope, locals);
var optionItem = new Option(selectValue, viewValue, label, group);
var disabled = disableWhenFn(scope, locals);
var optionItem = new Option(selectValue, viewValue, label, group, disabled);
optionItems.push(optionItem);
selectValueMap[selectValue] = optionItem;
@@ -307,6 +337,11 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
selectValueMap: selectValueMap,
getOptionFromViewValue: function(value) {
return selectValueMap[getTrackByValue(value, getLocals(value))];
},
getViewValueFromOption: function(option) {
// If the viewValue could be an object that may be mutated by the application,
// we need to make a copy and not return the reference to the value on the option.
return trackBy ? angular.copy(option.viewValue) : option.viewValue;
}
};
}
@@ -373,7 +408,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
selectCtrl.writeValue = function writeNgOptionsValue(value) {
var option = options.getOptionFromViewValue(value);
if (option) {
if (option && !option.disabled) {
if (selectElement[0].value !== option.selectValue) {
removeUnknownOption();
removeEmptyOption();
@@ -397,10 +432,10 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
var selectedOption = options.selectValueMap[selectElement.val()];
if (selectedOption) {
if (selectedOption && !selectedOption.disabled) {
removeEmptyOption();
removeUnknownOption();
return selectedOption.viewValue;
return options.getViewValueFromOption(selectedOption);
}
return null;
};
@@ -422,18 +457,22 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
if (value) {
value.forEach(function(item) {
var option = options.getOptionFromViewValue(item);
if (option) option.element.selected = true;
if (option && !option.disabled) option.element.selected = true;
});
}
};
selectCtrl.readValue = function readNgOptionsMultiple() {
var selectedValues = selectElement.val() || [];
return selectedValues.map(function(selectedKey) {
var option = options.selectValueMap[selectedKey];
return option.viewValue;
var selectedValues = selectElement.val() || [],
selections = [];
forEach(selectedValues, function(value) {
var option = options.selectValueMap[value];
if (!option.disabled) selections.push(options.getViewValueFromOption(option));
});
return selections;
};
}
@@ -461,11 +500,17 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
// We will re-render the option elements if the option values or labels change
scope.$watchCollection(ngOptions.getWatchables, updateOptions);
// We also need to watch to see if the internals of the model changes, since
// ngModel only watches for object identity change
if (ngOptions.trackBy) {
scope.$watch(attr.ngModel, function() { ngModelCtrl.$render(); }, true);
}
// ------------------------------------------------------------------ //
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;
@@ -601,10 +646,13 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
// 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)) {
if (ngOptions.trackBy && !equals(previousValue, nextValue) ||
previousValue !== nextValue) {
ngModelCtrl.$setViewValue(nextValue);
ngModelCtrl.$render();
}
}
}
}
-1
View File
@@ -180,7 +180,6 @@ var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale,
IS_WHEN = /^when(Minus)?(.+)$/;
return {
restrict: 'EA',
link: function(scope, element, attr) {
var numberExp = attr.count,
whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs
+62 -8
View File
@@ -33,8 +33,8 @@
* <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
* You need to be aware that the JavaScript specification does not define the order of keys
* returned 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
@@ -48,6 +48,55 @@
* 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.
@@ -115,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.
@@ -144,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:
+2 -2
View File
@@ -47,10 +47,10 @@
</example>
*/
var ngStyleDirective = ngDirective(function(scope, element, attr) {
scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) {
scope.$watch(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
+7 -4
View File
@@ -73,6 +73,7 @@ var SelectController =
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);
@@ -134,13 +135,13 @@ var SelectController =
* 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/).
* **Note:** By default, `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/). When using `track by`
* in an `ngOptions` expression, however, deep equality checks will be performed.
* </div>
*
*/
var selectDirective = function() {
var lastView;
return {
restrict: 'E',
@@ -199,11 +200,13 @@ var selectDirective = function() {
// 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
var lastView, lastViewRef = NaN;
scope.$watch(function selectMultipleWatch() {
if (!equals(lastView, ngModelCtrl.$viewValue)) {
if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
lastView = shallowCopy(ngModelCtrl.$viewValue);
ngModelCtrl.$render();
}
lastViewRef = ngModelCtrl.$viewValue;
});
// If we are a multiple select then value is now a collection
+14
View File
@@ -20,6 +20,13 @@
* Dependency Injected. To achieve this a filter definition consists of a factory function which is
* annotated with dependencies and is responsible for creating a filter function.
*
* <div class="alert alert-warning">
* **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
* Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
* your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
* (`myapp_subsection_filterx`).
* </div>
*
* ```js
* // Filter registration
* function MyModule($provide, $filterProvider) {
@@ -101,6 +108,13 @@ function $FilterProvider($provide) {
* @name $filterProvider#register
* @param {string|Object} name Name of the filter function, or an object map of filters where
* the keys are the filter names and the values are the filter factories.
*
* <div class="alert alert-warning">
* **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
* Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
* your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
* (`myapp_subsection_filterx`).
* </div>
* @returns {Object} Registered filter instance, or if a map of filters was provided then a map
* of the registered filter instances.
*/
+28 -6
View File
@@ -54,6 +54,9 @@
* - `false|undefined`: A short hand for a function which will look for a substring match in case
* insensitive way.
*
* Primitive values are converted to strings. Objects are not compared against primitives,
* unless they have a custom `toString` method (e.g. `Date` objects).
*
* @example
<example>
<file name="index.html">
@@ -132,14 +135,16 @@ function filterFilter() {
}
}
var expressionType = getTypeForFilter(expression);
var predicateFn;
var matchAgainstAnyProp;
switch (typeof expression) {
switch (expressionType) {
case 'function':
predicateFn = expression;
break;
case 'boolean':
case 'null':
case 'number':
case 'string':
matchAgainstAnyProp = true;
@@ -156,6 +161,10 @@ function filterFilter() {
};
}
function hasCustomToString(obj) {
return isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
}
// Helper functions for `filterFilter`
function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
@@ -165,8 +174,16 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
comparator = equals;
} else if (!isFunction(comparator)) {
comparator = function(actual, expected) {
if (isObject(actual) || isObject(expected)) {
// Prevent an object to be considered equal to a string like `'[object'`
if (isUndefined(actual)) {
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null)) {
// No substring matching against `null`; only match against `null`
return actual === expected;
}
if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
@@ -187,8 +204,8 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
}
function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
var actualType = typeof actual;
var expectedType = typeof expected;
var actualType = getTypeForFilter(actual);
var expectedType = getTypeForFilter(expected);
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
@@ -213,7 +230,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
} else if (expectedType === 'object') {
for (key in expected) {
var expectedVal = expected[key];
if (isFunction(expectedVal)) {
if (isFunction(expectedVal) || isUndefined(expectedVal)) {
continue;
}
@@ -234,3 +251,8 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc
return comparator(actual, expected);
}
}
// Used for easily differentiating between `null` and actual `object`
function getTypeForFilter(val) {
return (val === null) ? 'null' : typeof val;
}
+21 -9
View File
@@ -302,6 +302,14 @@ function ampmGetter(date, formats) {
return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
}
function eraGetter(date, formats) {
return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
}
function longEraGetter(date, formats) {
return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
}
var DATE_FORMATS = {
yyyy: dateGetter('FullYear', 4),
yy: dateGetter('FullYear', 2, 0, true),
@@ -328,10 +336,14 @@ var DATE_FORMATS = {
a: ampmGetter,
Z: timeZoneGetter,
ww: weekGetter(2),
w: weekGetter(1)
w: weekGetter(1),
G: eraGetter,
GG: eraGetter,
GGG: eraGetter,
GGGG: longEraGetter
};
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,
var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
NUMBER_STRING = /^\-?\d+$/;
/**
@@ -368,6 +380,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
* * `'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
* * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
* * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
* * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
*
* `format` string can also be one of the following predefined
* {@link guide/i18n localizable formats}:
@@ -393,7 +407,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.
*
@@ -486,12 +502,8 @@ function dateFilter($locale) {
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;
}
dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
date = convertTimezoneToLocal(date, timezone, true);
}
forEach(parts, function(value) {
fn = DATE_FORMATS[value];
+15 -2
View File
@@ -17,6 +17,8 @@
* 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`. 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.
*
@@ -88,7 +90,7 @@
</example>
*/
function limitToFilter() {
return function(input, limit) {
return function(input, limit, begin) {
if (Math.abs(Number(limit)) === Infinity) {
limit = Number(limit);
} else {
@@ -99,6 +101,17 @@ function limitToFilter() {
if (isNumber(input)) input = input.toString();
if (!isArray(input) && !isString(input)) return input;
return limit >= 0 ? input.slice(0, limit) : input.slice(limit);
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 {
if (begin === 0) {
return input.slice(limit, input.length);
} else {
return input.slice(Math.max(0, begin + limit), begin);
}
}
};
}
+38 -1
View File
@@ -17,7 +17,7 @@
* Can be one of:
*
* - `function`: Getter function. The result of this function will be sorted using the
* `<`, `=`, `>` operator.
* `<`, `===`, `>` operator.
* - `string`: An Angular expression. The result of this expression is used to compare elements
* (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
* 3 first characters of a property called `name`). The result of a constant expression
@@ -34,6 +34,43 @@
* @param {boolean=} reverse Reverse the order of the array.
* @returns {Array} Sorted copy of the source array.
*
*
* @example
* The example below demonstrates a simple ngRepeat, where the data is sorted
* by age in descending order (predicate is set to `'-age'`).
* `reverse` is not set, which means it defaults to `false`.
<example module="orderByExample">
<file name="index.html">
<script>
angular.module('orderByExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends =
[{name:'John', phone:'555-1212', age:10},
{name:'Mary', phone:'555-9876', age:19},
{name:'Mike', phone:'555-4321', age:21},
{name:'Adam', phone:'555-5678', age:35},
{name:'Julie', phone:'555-8765', age:29}];
}]);
</script>
<div ng-controller="ExampleController">
<table class="friend">
<tr>
<th>Name</th>
<th>Phone Number</th>
<th>Age</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:'-age'">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
</tr>
</table>
</div>
</file>
</example>
*
* The predicate and reverse parameters can be controlled dynamically through scope properties,
* as shown in the next example.
* @example
<example module="orderByExample">
<file name="index.html">
+108 -39
View File
@@ -9,6 +9,65 @@ var JSON_ENDS = {
};
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
function paramSerializerFactory(jQueryMode) {
function serializeValue(v) {
if (isObject(v)) {
return isDate(v) ? v.toISOString() : toJson(v);
}
return v;
}
return function paramSerializer(params) {
if (!params) return '';
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || isUndefined(value)) return;
if (isArray(value) || isObject(value) && jQueryMode) {
forEach(value, function(v, k) {
var keySuffix = jQueryMode ? '[' + (!isArray(value) ? k : '') + ']' : '';
parts.push(encodeUriQuery(key + keySuffix) + '=' + encodeUriQuery(serializeValue(v)));
});
} else {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
}
});
return parts.length > 0 ? parts.join('&') : '';
};
}
function $HttpParamSerializerProvider() {
/**
* @ngdoc service
* @name $httpParamSerializer
* @description
*
* Default $http params serializer that converts objects to a part of a request URL
* according to the following rules:
* * `{'foo': 'bar'}` results in `foo=bar`
* * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
* * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object)
* */
this.$get = function() {
return paramSerializerFactory(false);
};
}
function $HttpParamSerializerJQLikeProvider() {
/**
* @ngdoc service
* @name $httpParamSerializerJQLike
* @description
*
* Alternative $http params serializer that follows jQuerys `param()` method {http://api.jquery.com/jquery.param/} logic.
* */
this.$get = function() {
return paramSerializerFactory(true);
};
}
function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
// Strip json vulnerability protection prefix and trim whitespace
@@ -37,19 +96,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 +132,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);
@@ -148,6 +212,11 @@ function $HttpProvider() {
* - **`defaults.headers.put`**
* - **`defaults.headers.patch`**
*
* - **`defaults.paramSerializer`** - {string|function(Object<string,string>):string} - A function used to prepare string representation
* of request parameters (specified as an object).
* Is specified as string, it is interpreted as function registered in with the {$injector}.
* Defaults to {$httpParamSerializer}.
*
**/
var defaults = this.defaults = {
// transform incoming response data
@@ -169,7 +238,9 @@ function $HttpProvider() {
},
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN'
xsrfHeaderName: 'X-XSRF-TOKEN',
paramSerializer: '$httpParamSerializer'
};
var useApplyAsync = false;
@@ -183,7 +254,7 @@ function $HttpProvider() {
* significant performance improvement for bigger applications that make many HTTP requests
* concurrently (common during application bootstrap).
*
* Defaults to false. If no value is specifed, returns the current configured value.
* Defaults to false. If no value is specified, returns the current configured value.
*
* @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
* "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
@@ -215,11 +286,17 @@ function $HttpProvider() {
**/
var interceptorFactories = this.interceptors = [];
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
var defaultCache = $cacheFactory('$http');
/**
* Make sure that default param serializer is exposed as a function
*/
defaults.paramSerializer = isString(defaults.paramSerializer) ?
$injector.get(defaults.paramSerializer) : defaults.paramSerializer;
/**
* Interceptors stored in reverse order. Inner interceptors before outer interceptors.
* The reversal is needed so that we can build up the interception chain around the
@@ -372,7 +449,7 @@ function $HttpProvider() {
* headers: {
* 'Content-Type': undefined
* },
* data: { test: 'test' },
* data: { test: 'test' }
* }
*
* $http(req).success(function(){...}).error(function(){...});
@@ -631,6 +708,9 @@ function $HttpProvider() {
* response body, headers and status and returns its transformed (typically deserialized) version.
* See {@link ng.$http#overriding-the-default-transformations-per-request
* Overriding the Default Transformations}
* - **paramSerializer** - {string|function(Object<string,string>):string} - A function used to prepare string representation
* of request parameters (specified as an object).
* Is specified as string, it is interpreted as function registered in with the {$injector}.
* - **cache** `{boolean|Cache}` If true, a default $http cache will be used to cache the
* GET request, otherwise if a cache instance built with
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
@@ -759,11 +839,14 @@ function $HttpProvider() {
var config = extend({
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse
transformResponse: defaults.transformResponse,
paramSerializer: defaults.paramSerializer
}, requestConfig);
config.headers = mergeHeaders(requestConfig);
config.method = uppercase(config.method);
config.paramSerializer = isString(config.paramSerializer) ?
$injector.get(config.paramSerializer) : config.paramSerializer;
var serverRequest = function(config) {
var headers = config.headers;
@@ -807,6 +890,8 @@ function $HttpProvider() {
}
promise.success = function(fn) {
assertArgFn(fn, 'fn');
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
@@ -814,6 +899,8 @@ function $HttpProvider() {
};
promise.error = function(fn) {
assertArgFn(fn, 'fn');
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
@@ -1023,7 +1110,7 @@ function $HttpProvider() {
cache,
cachedResp,
reqHeaders = config.headers,
url = buildUrl(config.url, config.params);
url = buildUrl(config.url, config.paramSerializer(config.params));
$http.pendingRequests.push(config);
promise.then(removePendingReq, removePendingReq);
@@ -1061,7 +1148,7 @@ function $HttpProvider() {
// send the request to the backend
if (isUndefined(cachedResp)) {
var xsrfValue = urlIsSameOrigin(config.url)
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
: undefined;
if (xsrfValue) {
reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
@@ -1130,27 +1217,9 @@ function $HttpProvider() {
}
function buildUrl(url, params) {
if (!params) return url;
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || isUndefined(value)) return;
if (!isArray(value)) value = [value];
forEach(value, function(v) {
if (isObject(v)) {
if (isDate(v)) {
v = v.toISOString();
} else {
v = toJson(v);
}
}
parts.push(encodeUriQuery(key) + '=' +
encodeUriQuery(v));
});
});
if (parts.length > 0) {
url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
function buildUrl(url, serializedParams) {
if (serializedParams.length > 0) {
url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams;
}
return url;
}
+1 -1
View File
@@ -137,7 +137,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
};
function jsonpReq(url, callbackId, done) {
// we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
// we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
var script = rawDocument.createElement('script'), callback = null;
+17 -13
View File
@@ -1,6 +1,16 @@
'use strict';
var $interpolateMinErr = minErr('$interpolate');
var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
$interpolateMinErr.throwNoconcat = function(text) {
throw $interpolateMinErr('noconcat',
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
"interpolations that concatenate multiple expressions when a trusted value is " +
"required. See http://docs.angularjs.org/api/ng.$sce", text);
};
$interpolateMinErr.interr = function(text, err) {
return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
};
/**
* @ngdoc provider
@@ -244,10 +254,7 @@ function $InterpolateProvider() {
// make it obvious that you bound the value to some user controlled value. This helps reduce
// the load when auditing for XSS issues.
if (trustedContext && concat.length > 1) {
throw $interpolateMinErr('noconcat',
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
"interpolations that concatenate multiple expressions when a trusted value is " +
"required. See http://docs.angularjs.org/api/ng.$sce", text);
$interpolateMinErr.throwNoconcat(text);
}
if (!mustHaveExpression || expressions.length) {
@@ -277,16 +284,14 @@ function $InterpolateProvider() {
return compute(values);
} catch (err) {
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
err.toString());
$exceptionHandler(newErr);
$exceptionHandler($interpolateMinErr.interr(text, err));
}
}, {
// all of these properties are undocumented for now
exp: text, //just for compatibility with regular watchers created via $watch
expressions: expressions,
$$watchDelegate: function(scope, listener, objectEquality) {
$$watchDelegate: function(scope, listener) {
var lastValue;
return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
var currValue = compute(values);
@@ -294,7 +299,7 @@ function $InterpolateProvider() {
listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
}
lastValue = currValue;
}, objectEquality);
});
}
});
}
@@ -304,9 +309,7 @@ function $InterpolateProvider() {
value = getValue(value);
return allOrNothing && !isDefined(value) ? value : stringify(value);
} catch (err) {
var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text,
err.toString());
$exceptionHandler(newErr);
$exceptionHandler($interpolateMinErr.interr(text, err));
}
}
}
@@ -347,3 +350,4 @@ function $InterpolateProvider() {
}];
}
+7 -2
View File
@@ -39,6 +39,7 @@ function $IntervalProvider() {
* indefinitely.
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
* will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
* @param {...*=} Pass additional parameters to the executed function.
* @returns {promise} A promise which will be notified on each iteration.
*
* @example
@@ -132,7 +133,9 @@ function $IntervalProvider() {
* </example>
*/
function interval(fn, delay, count, invokeApply) {
var setInterval = $window.setInterval,
var hasParams = arguments.length > 4,
args = hasParams ? sliceArgs(arguments, 4) : [],
setInterval = $window.setInterval,
clearInterval = $window.clearInterval,
iteration = 0,
skipApply = (isDefined(invokeApply) && !invokeApply),
@@ -141,7 +144,9 @@ function $IntervalProvider() {
count = isDefined(count) ? count : 0;
promise.then(null, null, fn);
promise.then(null, null, (!hasParams) ? fn : function() {
fn.apply(null, args);
});
promise.$$intervalId = setInterval(function tick() {
deferred.notify(iteration++);
+9 -1
View File
@@ -59,7 +59,15 @@ function $LocaleProvider() {
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
mediumTime: 'h:mm:ss a',
shortTime: 'h:mm a'
shortTime: 'h:mm a',
ERANAMES: [
"Before Christ",
"Anno Domini"
],
ERAS: [
"BC",
"AD"
]
},
pluralCat: function(num) {
+1 -1
View File
@@ -882,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);
}
+1
View File
@@ -32,6 +32,7 @@
<button ng-click="$log.warn(message)">warn</button>
<button ng-click="$log.info(message)">info</button>
<button ng-click="$log.error(message)">error</button>
<button ng-click="$log.debug(message)">debug</button>
</div>
</file>
</example>
+30 -19
View File
@@ -1,5 +1,16 @@
'use strict';
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Any commits to this file should be reviewed with security in mind. *
* Changes to this file can potentially create security vulnerabilities. *
* An approval from 2 Core members with history of modifying *
* this file is required. *
* *
* Does the change somehow allow for arbitrary javascript to be executed? *
* Or allows for someone to change the prototype of built-in objects? *
* Or gives undesired access to variables likes document or window? *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
var $parseMinErr = minErr('$parse');
// Sandboxing Angular Expressions
@@ -773,7 +784,7 @@ ASTCompiler.prototype = {
self.state.computing = fnKey;
var intoId = self.nextId();
self.recurse(watch, intoId);
self.return(intoId);
self.return_(intoId);
self.state.inputs.push(fnKey);
watch.watchId = key;
});
@@ -860,7 +871,7 @@ ASTCompiler.prototype = {
recursionFn = recursionFn || noop;
if (!skipWatchIdCheck && isDefined(ast.watchId)) {
intoId = intoId || this.nextId();
this.if('i',
this.if_('i',
this.lazyAssign(intoId, this.computedMember('i', ast.watchId)),
this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true)
);
@@ -873,7 +884,7 @@ ASTCompiler.prototype = {
if (pos !== ast.body.length - 1) {
self.current().body.push(right, ';');
} else {
self.return(right);
self.return_(right);
}
});
break;
@@ -904,13 +915,13 @@ ASTCompiler.prototype = {
case AST.LogicalExpression:
intoId = intoId || this.nextId();
self.recurse(ast.left, intoId);
self.if(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId));
recursionFn(intoId);
break;
case AST.ConditionalExpression:
intoId = intoId || this.nextId();
self.recurse(ast.test, intoId);
self.if(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId));
recursionFn(intoId);
break;
case AST.Identifier:
@@ -921,11 +932,11 @@ ASTCompiler.prototype = {
nameId.name = ast.name;
}
ensureSafeMemberName(ast.name);
self.if(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
function() {
self.if(self.stage === 'inputs' || 's', function() {
self.if_(self.stage === 'inputs' || 's', function() {
if (create && create !== 1) {
self.if(
self.if_(
self.not(self.nonComputedMember('s', ast.name)),
self.lazyAssign(self.nonComputedMember('s', ast.name), '{}'));
}
@@ -942,13 +953,13 @@ ASTCompiler.prototype = {
left = nameId && (nameId.context = this.nextId()) || this.nextId();
intoId = intoId || this.nextId();
self.recurse(ast.object, left, undefined, function() {
self.if(self.notNull(left), function() {
self.if_(self.notNull(left), function() {
if (ast.computed) {
right = self.nextId();
self.recurse(ast.property, right);
self.addEnsureSafeMemberName(right);
if (create && create !== 1) {
self.if(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
}
expression = self.ensureSafeObject(self.computedMember(left, right));
self.assign(intoId, expression);
@@ -959,7 +970,7 @@ ASTCompiler.prototype = {
} else {
ensureSafeMemberName(ast.property.name);
if (create && create !== 1) {
self.if(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
}
expression = self.nonComputedMember(left, ast.property.name);
if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
@@ -993,10 +1004,10 @@ ASTCompiler.prototype = {
left = {};
args = [];
self.recurse(ast.callee, right, left, function() {
self.if(self.notNull(right), function() {
self.if_(self.notNull(right), function() {
self.addEnsureSafeFunction(right);
forEach(ast.arguments, function(expr) {
self.recurse(expr, undefined, undefined, function(argument) {
self.recurse(expr, self.nextId(), undefined, function(argument) {
args.push(self.ensureSafeObject(argument));
});
});
@@ -1022,19 +1033,19 @@ ASTCompiler.prototype = {
throw $parseMinErr('lval', 'Trying to assing a value to a non l-value');
}
this.recurse(ast.left, undefined, left, function() {
self.if(self.notNull(left.context), function() {
self.if_(self.notNull(left.context), function() {
self.recurse(ast.right, right);
self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
self.assign(intoId, expression);
recursionFn(expression);
recursionFn(intoId || expression);
});
}, 1);
break;
case AST.ArrayExpression:
args = [];
forEach(ast.elements, function(expr) {
self.recurse(expr, undefined, undefined, function(argument) {
self.recurse(expr, self.nextId(), undefined, function(argument) {
args.push(argument);
});
});
@@ -1045,7 +1056,7 @@ ASTCompiler.prototype = {
case AST.ObjectExpression:
args = [];
forEach(ast.properties, function(property) {
self.recurse(property.value, undefined, undefined, function(expr) {
self.recurse(property.value, self.nextId(), undefined, function(expr) {
args.push(self.escape(
property.key.type === AST.Identifier ? property.key.name :
('' + property.key.value)) +
@@ -1097,11 +1108,11 @@ ASTCompiler.prototype = {
return 'plus(' + left + ',' + right + ')';
},
'return': function(id) {
return_: function(id) {
this.current().body.push('return ', id, ';');
},
'if': function(test, alternate, consequent) {
if_: function(test, alternate, consequent) {
if (test === true) {
alternate();
} else {
+28 -19
View File
@@ -80,9 +80,27 @@ function $RootScopeProvider() {
return TTL;
};
function createChildScopeClass(parent) {
function ChildScope() {
this.$$watchers = this.$$nextSibling =
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$id = nextUid();
this.$$ChildScope = null;
}
ChildScope.prototype = parent;
return ChildScope;
}
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
function($injector, $exceptionHandler, $parse, $browser) {
function destroyChildScope($event) {
$event.currentScope.$$destroyed = true;
}
/**
* @ngdoc type
* @name $rootScope.Scope
@@ -206,16 +224,7 @@ function $RootScopeProvider() {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if (!this.$$ChildScope) {
this.$$ChildScope = function ChildScope() {
this.$$watchers = this.$$nextSibling =
this.$$childHead = this.$$childTail = null;
this.$$listeners = {};
this.$$listenerCount = {};
this.$$watchersCount = 0;
this.$id = nextUid();
this.$$ChildScope = null;
};
this.$$ChildScope.prototype = this;
this.$$ChildScope = createChildScopeClass(this);
}
child = new this.$$ChildScope();
}
@@ -233,13 +242,9 @@ function $RootScopeProvider() {
// prototypically. In all other cases, this property needs to be set
// when the parent scope is destroyed.
// The listener needs to be added after the parent is set
if (isolate || parent != this) child.$on('$destroy', destroyChild);
if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
return child;
function destroyChild() {
child.$$destroyed = true;
}
},
/**
@@ -866,13 +871,17 @@ function $RootScopeProvider() {
* clean up DOM bindings before an element is removed from the DOM.
*/
$destroy: function() {
// we can't destroy the root scope or a scope that has been already destroyed
// We can't destroy a scope that has been already destroyed.
if (this.$$destroyed) return;
var parent = this.$parent;
this.$broadcast('$destroy');
this.$$destroyed = true;
if (this === $rootScope) return;
if (this === $rootScope) {
//Remove handlers attached to window when $rootScope is removed
$browser.$$applicationDestroyed();
}
incrementWatchersCount(this, -this.$$watchersCount);
for (var eventName in this.$$listenerCount) {
@@ -881,8 +890,8 @@ function $RootScopeProvider() {
// sever all the references to parent scopes (after this cleanup, the current scope should
// not be retained by any of our references and should be eligible for garbage collection)
if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;

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