Compare commits

..

67 Commits

Author SHA1 Message Date
Thomas Landauer 46b7cf7464 docs(filter): document third argument of predicate function 2015-06-15 20:50:59 +02:00
Martin Staffa 860edee65b docs(changelog): fix typo
Closes #12085
2015-06-15 20:45:28 +02:00
Caitlin Potter ebd0fbba8f fix(forms): parse exponential notation in numberInputType parser
Support parsing numbers in exponential notation, which Number.prototype.toString() returns
for sufficiently high numbers.

Closes #12121
Closes #12122
2015-06-15 08:46:47 -04:00
Rouven Weßling 093416f60f chore(npm): add the license to package.json
This removes a warning when executing npm install.

Closes #12111
2015-06-15 14:17:56 +03:00
Martin Staffa 0400dc9c2a docs($http): expand the param serializer docs
Closes #11745
Closes #12064
2015-06-14 13:36:37 +02:00
Lucas Galfaso 71fc3f4fa0 fix($parse): set null reference properties to undefined
When there is an expression of the form
* true && a.b.c
* true && a()
* true && a()()
* false || a.b.c
* false || a()
* false || a()()

where `a == null`

Closes #12099
2015-06-13 12:38:57 +02:00
Martin Staffa 8caf1802e0 fix(compile): assign ctrl return values correctly for multiple directives
Fixes #12029
Closes #12036
2015-06-12 21:41:55 +02:00
Peter Bacon Darwin 91b602263b fix($location): do not get caught in infinite digest in IE9
Thanks to @hamfastgamgee for getting this fix in place.

Closes #11439
Closes #11675
Closes #11935
Closes #12083
2015-06-12 14:35:46 +01:00
Lucas Galfaso 0934b76b72 fix(ngModel): form validation when there is an Object.prototype enumerable value
When adding an Object.prototype enumerable property, this should not be confused
as a form error

Closes #12066
2015-06-11 23:25:08 +02:00
Caitlin Potter 571bee7b2f test($location): ensure mock window can be wrapped by jqLite
Fixin da build

Closes #12086
2015-06-11 12:04:56 -04:00
Peter Bacon Darwin 11055132bf test($locationSpec): refactor and clean up tests 2015-06-11 14:29:13 +01:00
Peter Bacon Darwin 0898b1240b test($logSpec): don't pollute the global namespace with helpers 2015-06-11 13:29:15 +01:00
Conny Sjöblom 8dc09e6dab fix(linky): allow case insensitive scheme detection
Closes #12073
Closes #12073
2015-06-10 14:38:55 +01:00
Lucas Galfaso 799353c75d fix($sanitize): dont not remove tab index property
Closes #8371
Closes #5853
2015-06-10 11:48:00 +02:00
David Link ffac747e84 docs(tutorial/Tutorial): clarify what npm install does
If the reader does not cd into angular-phonecat subdirectory,
npm install will not work.
This comment helps newbies understand npm install is dependent
on a custom project.json file.

Closes #12040
2015-06-09 21:41:12 +02:00
Martin Staffa 998340de7f docs(select): correct workaround for numeric option bc 2015-06-09 21:35:59 +02:00
Georgios Kalpakas 559313652e test($compile): fix test
The fixed test is supposed to test a fix for an IE11 bug/peculiarity that
arises when using a specifically configured MutationObserver on the page
(see #11781 for more info).
The configuration contained a typo (`sublist` instead of `subtree`), which
effectively failed to set up the MutationObserver in a way that would make
the IE11 bug appear.

Closes #12061
2015-06-09 18:29:26 +03:00
Martin Staffa a69251ab55 docs(select): add note about breaking change with numeric values
Closes #12052
2015-06-08 21:15:14 +02:00
David Anderton 7e5248a33f docs(orderBy): correctly is not the right word
The use of correctly implies that Angular is doing something incorrect, however it is that we expect a number passed as a string to be sorted as a number. Angular does not do what we are expecting, although it responds correctly to what we have actually asked - sorting based on the string representation of a number.

Closes #12046
2015-06-08 21:15:10 +02:00
Martin Staffa c210ff5eae docs($compile): correct what gets passed to ctrl argument
Closes #11903
2015-06-08 21:15:07 +02:00
Jason Bedard 9efb0d5ee9 perf($compile): avoid jquery data calls when there is no data 2015-06-08 12:16:09 +02:00
Jason Bedard 0e622f7b5b fix(copy): do not copy the same object twice 2015-06-08 12:12:56 +02:00
Martin Staffa 071be60927 docs($animateCss): fix a dangling link 2015-06-06 14:45:01 +02:00
Martin Staffa a25aa5b577 docs(input[number]): mention incompatibility with allowInvalid
Closes #11390
2015-06-06 14:44:56 +02:00
Josh Sherick df9c720d08 docs(tutorial/step0): display list correctly
Two bullet points were indented causing the entire lines
to be formatted as code by markdown.
Decreased indentation level by one, as it seems this was not the intention.

Closes #12035
2015-06-06 14:44:52 +02:00
Will Fong 4fe141f794 docs(tutorial/index): add a "next step" sentence
All the other pages have a "continue to next page" link
at the bottom of the page. Keeps a nice reading flow.

Closes #12038
2015-06-06 14:44:28 +02:00
Lucas Galfaso f3b1d0b723 fix($compile): workaround for IE11 MutationObserver
IE11 MutationObserver breaks consecutive text nodes into several text nodes.
This patch merges consecutive text nodes into a single node before
looking for interpolations.

Closes #11781
2015-06-06 11:26:40 +02:00
qbzzt 288225b080 docs(ngClass): add class 'has-error' to demonstrate hyphen use
Modify the example, to show that, when using `ngClass`'s map syntax,
hyphenated classes (e.h. such as those used by Bootstrap) must be enclosed
in quotes.

Closes #12027
2015-06-06 01:34:31 +03:00
Matias Niemelä e967abcd30 docs(CHANGELOG): add changes for 1.3.16 2015-06-05 13:40:38 -07:00
Igor Minar 2c3cd8126c docs(http): add info about unique cookies and XSRF protection on shared domains
Closes #12028
2015-06-04 22:15:55 -07:00
Martin Staffa 41385f0afc docs(*): improve accessibility of links
Closes #7932
2015-06-04 23:00:43 +02:00
Martin Staffa 500b0f6cdb docs(guide/expressions): include filters in one-time binding examples
Closes #8776
2015-06-04 23:00:40 +02:00
Allan Bogh 40b27280ab docs(ngAria): clarify which module to include for ngAria
Closes #11802
2015-06-04 23:00:37 +02:00
Kevin Huang b73c64e2fb docs(guide/Conceptual Overview): remove text from in-page anchor tags
The in-page anchor tags that serve as bookmarks for information about views and models have
visible text content that unintentionally makes them seem like clickable links navigating to
other parts of the page or to entirely different pages.

Closes #11450
2015-06-04 23:00:34 +02:00
fahdsheikh 2f5d42ec72 docs(changelog): fixed grammatical error
Changed "a animation" to "an animation" for grammatical consistency

Closes #11672
2015-06-04 23:00:31 +02:00
Georgios Kalpakas 25d731e9b0 docs(orderBy): fix JSCS trailing whitespace error 2015-06-04 10:55:07 +03:00
Robert Jenks 15da7cc3dc docs(orderBy): improve sorting behaviour and move logic into the controller
Improve the sorting behaviour in the 2nd example: Clicking on an unsorted
column sorts in ascending order, while clicking on a sorted column sorts
in descending order. Also, add a simple sort indicator.

Closes #11981
2015-06-04 10:11:16 +03:00
Peter Bacon Darwin 34a6da24c1 fix(ngOptions): do not watch properties starting with $
Expressions that compute labels and track by values for ngOptions were
being called for properties, that start with $ even though those properties
were being ignored as options.

Closes #11930
Closes #12010
2015-06-04 06:28:00 +01:00
Georgios Kalpakas ebaa0f5985 fix(ngAria): update aria-valuemin/max when min/max change
As a result of thi fix, `ngMin/Max` also set `aria-valuemin/max` on
"range"-shaped elements.

Fixes #11770

Closes #11774
2015-06-03 21:23:38 +02:00
Dominick bb15d414c6 docs(guide/Modules): simple grammar fix
the module consist of… --> the module consists of…

Closes #11843
2015-06-03 21:07:46 +02:00
Dominick f056036a4a docs(guide/security): remove errant word
Closes #11870
2015-06-03 21:06:44 +02:00
Dominick a055762027 docs(guide/i18n): grammar fix
contraction "it's" --> possessive "its"
2015-06-03 21:06:19 +02:00
Dominick 82d38e4453 docs(guide/Using $location): format parameter name
Format parameter name as code. (It's used elsewhere in this doc page in code examples, as code itself.)
2015-06-03 21:04:13 +02:00
Steve Mao 6a743f0b5b docs(ngModelOptions): make object notation style consistent
There is a space before and after `{` and `}` for most of the objects throughout the docs.

Closes #11871
2015-06-03 21:03:16 +02:00
shoja 31f6f76291 docs($httpBackend): correct typo
docs($httpBackend): correct typo

Correct "send" to "sent".

Closes #11876
2015-06-03 21:02:50 +02:00
David Eriksson a2b5a5ed5f docs($cookiesProvider): escape HTML
Escape the "base" HTML element so it will be displayed in the online documentation.

Closes #11897
2015-06-03 21:01:47 +02:00
Boris Cherny 1d41e8a975 docs(ngRepeat): document that track by must be the last expression
this is a point of confusion that's not well documented. see #5520

Closes #11934
2015-06-03 20:57:56 +02:00
Pascal Precht 01182dfbb8 docs(ngMessages): fixes logical bug
The paragraph below this changes says:

"Then the `required` message will be displayed first."

`required` needs to be `true` to match that sentence.

Closes #12009
2015-06-03 19:59:26 +02:00
Yuriy Bash e17f85cc5b docs(ngMessages): fix spelling error
Closes #12019
2015-06-03 19:56:51 +02:00
Steven Easter d7dc14dc0c fix(ngOptions): use reference check only when not using trackBy
Change the check on changed value for ngOptions to ensure that a reference check is only done
when trackBy is not used.  Previously, if trackBy was being used but the values of the objects were
equal a reference check was then used which would cause the check to be true if the objects had
different memory references but the values were equal.  This can cause issues with forms being
marked dirty when the value of the model as not actually changed.

Closes #11936
Closes #11996
2015-06-02 12:37:33 +01:00
Henry Zhu 578fa019b3 chore(jscs): remove .jscs.json.todo, rename config to .jscsrc
Closes #11993
2015-06-02 10:29:54 +01:00
Michal Miszczyszyn 3c9096efb4 test(ngAria): test that aria-hidden/disabled are always "true" or "false"
Previously, when using ngAria with the ng-hide directive,
and the value passed to ng-hide was not boolean,
the aria-hidden attribute was set to this non-boolean value.

Closes #11865
Closes #11998
2015-06-01 23:13:22 +02:00
Fred Sauer 59273354b5 fix(ngAria): ensure boolean values for aria-hidden and aria-disabled
aria-hidden should mirror the boolean representation of their ng-*
counterpart (ng-show, ng-hide) instead of their actual value. Same
applies to aria-disabled and ng-disabled

Closes #11365
2015-06-01 23:13:21 +02:00
Xavier Haniquaut f67204794e docs(migration): fix minor typo
Closes #11994
2015-06-01 21:51:47 +01:00
Wenhua Zhao b6389eedda chore(injector): avoid invoking noop 2015-06-01 11:48:36 +02:00
Ran Ding a6339d30d1 fix($compile): exception when using "watch" as isolated scope binding variable in Firefox
Fix on all binding modes: '=', '@' and '&' as well as optional cases
Throw exception when user define 'hasOwnProperty' in binding.

Closes #11627
2015-06-01 11:36:18 +02:00
Vladimir Lugovsky 351fe4b79c feat($compile): show module name during multidir error
Show module name if possible when multidir error happens.

Closes #11775
2015-06-01 11:08:27 +02:00
Lucas Galfaso d19504a179 fix($parse): set null reference properties to undefined
When there is an expression of the form `true && a.b` and where `a == null`, then set
the value of `true && a.b` to `undefined`.

Closes #11959
2015-06-01 11:03:12 +02:00
Jeff Lee e5e871fe1a docs(ngAnimate): fix typo
Closes #11957
2015-06-01 08:03:20 +01:00
Ron Tsui 90fa884f94 docs(README): improve unusual phrasing
Closes #11958
2015-06-01 08:01:26 +01:00
Yi EungJun 80b9018f29 docs(guide/Directives): use more standard data-ng-model in example
Use data-ng-model instead of data-ng:model which is accepted for legacy reason.
The next "Normalization" section is saying:

> Best Practice: Prefer using the dash-delimited format (e.g. ng-bind for
> ngBind). If you want to use an HTML validating tool, you can instead use the
> data-prefixed version (e.g. data-ng-bind for ngBind). The other forms
> shown above are accepted for legacy reasons but we advise you to avoid
> them.

Closes #11960
2015-06-01 07:59:05 +01:00
Daniel f6ac226c8b docs(numberFilter): update to match handling of null and undefined
This changed in 2ae10f67fc, where null and
undefined are passed through.

Closes #11963
2015-06-01 07:55:37 +01:00
Michael Watts 2d22380873 docs(guide/Scopes): capitalisation of word scope
Closes #11970
2015-06-01 07:50:12 +01:00
Peter Bacon Darwin 7c49d9986f docs(README.closure.md): clarify sentence
Closes #11979
2015-06-01 07:48:21 +01:00
pholly 209f4f3e0f docs(guide/Expressions): added special case for one-time binding of object literals under Value stabilization algorithm
One time binding of object literals are treated differently than simple expressions. Added a link to Ben Nadel's article describing how object literals's keys are checked for undefined.

Closes #11982
2015-06-01 07:42:22 +01:00
Tero Parviainen 5d68c763e2 refactor($compile): remove unused elementTransclusion argument
Remove the unused elementTransclusion argument from createBoundTranscludeFn.
Also remove the nodeLinkFn.elementTranscludeOnThisElement attribute, which
becomes unnecessary.

Closes #9962
Closes #11985
2015-06-01 07:36:36 +01:00
Ryan Dale 3ef529806f feat($q): $q.resolve as an alias for $q.when
New "when" alias "resolve" to maintain naming consistency with ES6.

Closes #11944
Closes #11987
2015-06-01 07:31:51 +01:00
58 changed files with 2081 additions and 931 deletions
-15
View File
@@ -1,15 +0,0 @@
// This is an incomplete TODO list of checks we want to start enforcing
//
// The goal is to enable these checks one by one by moving them to .jscs.json along with commits
// that correct the existing code base issues and make the new check pass.
{
"validateParameterSeparator": ", ", // Re-assert this rule when JSCS allows multiple spaces
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"disallowImplicitTypeConversion": ["string"],
"disallowMultipleLineBreaks": true,
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
}
}
View File
+84 -2
View File
@@ -1,3 +1,59 @@
<a name="1.3.16"></a>
# 1.3.16 cookie-oatmealification (2015-06-05)
## Bug Fixes
- **$compile:** throw error on invalid directive name
([634e4671](https://github.com/angular/angular.js/commit/634e467172efa696eb32ef8942ffbedeecbd030e),
[#11281](https://github.com/angular/angular.js/issues/11281), [#11109](https://github.com/angular/angular.js/issues/11109))
- **$cookies:** update $cookies to prevent duplicate cookie writes and play nice with external code
([706a93ab](https://github.com/angular/angular.js/commit/706a93ab6960e3474698ccf9a8048b3c32e567c6),
[#11490](https://github.com/angular/angular.js/issues/11490), [#11515](https://github.com/angular/angular.js/issues/11515))
- **$http:** throw error if `success` and `error` methods do not receive a function
([731e1f65](https://github.com/angular/angular.js/commit/731e1f6534ab7fd1e053b8d7a25c902fcd934fea),
[#11330](https://github.com/angular/angular.js/issues/11330), [#11333](https://github.com/angular/angular.js/issues/11333))
- **core:** ensure that multiple requests to requestAnimationFrame are buffered
([0adc0364](https://github.com/angular/angular.js/commit/0adc0364265b06c567ccc8e90a7f09cc46f235b2),
[#11791](https://github.com/angular/angular.js/issues/11791))
- **filterFilter:** fix matching against `null`/`undefined`
([9dd0fe35](https://github.com/angular/angular.js/commit/9dd0fe35d1027e59b84b2396abee00d8683f3b50),
[#11573](https://github.com/angular/angular.js/issues/11573), [#11617](https://github.com/angular/angular.js/issues/11617))
- **jqLite:**
- check for "length" in obj in isArrayLike to prevent iOS8 JIT bug from surfacing
([647f3f55](https://github.com/angular/angular.js/commit/647f3f55eb7100a255272f7277f0f962de234a32),
[#11508](https://github.com/angular/angular.js/issues/11508))
- attr should ignore comment, text and attribute nodes
([181e5ebc](https://github.com/angular/angular.js/commit/181e5ebc3fce5312feacaeace4fcad0d32f4d73c))
- **ngAnimate:**
- ensure that minified repaint code isn't removed
([d5c99ea4](https://github.com/angular/angular.js/commit/d5c99ea42b834343fd0362cfc572f47e7536ccfb),
[#9936](https://github.com/angular/angular.js/issues/9936))
- **ngAria:** handle elements with role="checkbox/menuitemcheckbox"
([1c282af5](https://github.com/angular/angular.js/commit/1c282af5abc205d4aac37c05c5cb725d71747134),
[#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
([b64519fe](https://github.com/angular/angular.js/commit/b64519fea7f1a5ec75e32c4b71b012b827314153),
[#11315](https://github.com/angular/angular.js/issues/11315), [#11411](https://github.com/angular/angular.js/issues/11411))
- **ngTouch:**
- check undefined tagName for SVG event target
([7560a8d2](https://github.com/angular/angular.js/commit/7560a8d2d65955ddb60ede9d586502f4e3cbd062))
- register touches properly when jQuery is used
([40441f6d](https://github.com/angular/angular.js/commit/40441f6dfc5ebd5cdc679c269c4639238f5351eb),
[#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:** prevent unknown option being added to select when bound to null property
([9e3f82bb](https://github.com/angular/angular.js/commit/9e3f82bbaf83cad7bb3121db756099b0880562e6),
[#11872](https://github.com/angular/angular.js/issues/11872), [#11875](https://github.com/angular/angular.js/issues/11875))
## Features
- **travis:** run unit tests on iOS 8
([1f650871](https://github.com/angular/angular.js/commit/1f650871266b88b3dab4a894a839a82ac9a06b69),
[#11479](https://github.com/angular/angular.js/issues/11479))
<a name="1.4.0"></a>
# 1.4.0 jaracimrman-existence (2015-05-26)
@@ -374,7 +430,7 @@ To get the desired behaviour you need to iterate using the object form of the `n
## Breaking Changes
- **$animate:** due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
JavaSript and CSS animations can no longer be run in
JavaScript 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
@@ -420,7 +476,7 @@ $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
`$scope.$digest` inside of an animation promise callback anymore
since the promise is resolved within a digest automatically (but a
digest is not run unless the promise is chained).
@@ -1153,7 +1209,33 @@ But in practice this is not what people want and so this change iterates over pr
in the order they are returned by Object.keys(obj), which is almost always the order
in which the properties were defined.
- **select:** due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
the `select` directive will now use strict comparison of the `ngModel` scope value against `option`
values to determine which option is selected. This means `Number` scope values will not be matched
against numeric option strings.
In Angular 1.3.x, setting `scope.x = 200` would select the `option` with the value 200 in the following `select`:
```
<select ng-model="x">
<option value="100">100</option>
<option value="200">200</option>
</select>
```
In Angular 1.4.x, the 'unknown option' will be selected.
To remedy this, you can simply initialize the model as a string: `scope.x = '200'`, or if you want to
keep the model as a `Number`, you can do the conversion via `$formatters` and `$parsers` on `ngModel`:
```js
ngModelCtrl.$parsers.push(function(value) {
return parseInt(value, 10); // Convert option value to number
});
ngModelCtrl.$formatters.push(function(value) {
return value.toString(); // Convert scope value to string
});
```
<a name="1.3.9"></a>
# 1.3.9 multidimensional-awareness (2015-01-13)
+1 -1
View File
@@ -158,7 +158,7 @@ module.exports = function(grunt) {
jscs: {
src: ['src/**/*.js', 'test/**/*.js'],
options: {
config: ".jscs.json"
config: ".jscsrc"
}
},
+2 -2
View File
@@ -1,8 +1,8 @@
Using AngularJS with the Closure Compiler
=========================================
The Closure Compiler project contains externs definitions for AngularJS
JavaScript in its `contrib/externs` directory.
The Closure Compiler project contains definitions for the AngularJS JavaScript
in its `contrib/externs` directory.
The definitions contain externs for use with the Closure compiler (aka
JSCompiler). Passing these files to the --externs parameter of a compiler
+1 -1
View File
@@ -10,7 +10,7 @@ the browser how to do dependency injection and inversion of control.
Oh yeah and it helps with server-side communication, taming async callbacks with promises and
deferreds. It also makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
piece of cake. The best of all: it makes development fun!
piece of cake. Best of all?? It makes development fun!
* Web site: http://angularjs.org
* Tutorial: http://docs.angularjs.org/tutorial
+1 -1
View File
@@ -43,7 +43,7 @@ angular.module('tutorials', [])
'<a href="http://angular.github.io/angular-phonecat/step-{{step}}/app">Step {{step}} Live Demo</a>.</p>\n' +
'</div>\n' +
'<p>The most important changes are listed below. You can see the full diff on ' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}">GitHub</a>\n' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}" title="See diff on Github">GitHub</a>\n' +
'</p>'
};
});
@@ -76,7 +76,7 @@
<div class="row">
<div class="col-md-9 header-branding">
<a class="brand navbar-brand" href="http://angularjs.org">
<img width="117" height="30" class="logo" ng-src="img/angularjs-for-header-only.svg">
<img width="117" height="30" class="logo" alt="Link to Angular JS Homepage" ng-src="img/angularjs-for-header-only.svg">
</a>
<ul class="nav navbar-nav">
<li class="divider-vertical"></li>
@@ -223,7 +223,7 @@
Super-powered by Google ©2010-2015
( <a id="version"
ng-href="https://github.com/angular/angular.js/blob/master/CHANGELOG.md#{{versionNumber}}"
ng-bind-template="v{{version}}">
ng-bind-template="v{{version}}" title="Changelog of this version of Angular JS">
</a>
)
</p>
+1 -1
View File
@@ -693,7 +693,7 @@ A path should always begin with forward slash (`/`); the `$location.path()` sett
forward slash if it is missing.
Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually
hashPrefix.
`hashPrefix`.
## Crawling your app
+2 -2
View File
@@ -57,7 +57,7 @@ Try out the Live Preview above, and then let's walk through the example and desc
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 <a name="compiler">{@link compiler compiler}</a>.
The loaded, transformed and rendered DOM is then called the <a name="view">view</a>.
The loaded, transformed and rendered DOM is then called the <a name="view"></a>*view*.
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
@@ -79,7 +79,7 @@ An <a name="expression">{@link expression expression}</a> in a template is a Jav
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>
The values that are stored in variables on the scope are referred to as the <a name="model"></a>*model*
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".
+1 -1
View File
@@ -51,7 +51,7 @@ In the following example, we say that the `<input>` element **matches** the `ngM
The following also **matches** `ngModel`:
```html
<input data-ng:model="foo">
<input data-ng-model="foo">
```
### Normalization
+14 -11
View File
@@ -28,13 +28,13 @@ Angular expressions are like JavaScript expressions with the following differenc
* **No Control Flow Statements:** You cannot use the following in an Angular expression:
conditionals, loops, or exceptions.
* **No Function Declarations:** You cannot declare functions in an Angular expression,
even inside `ng-init` directive.
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
* **No RegExp Creation With Literal Notation:** You cannot create regular expressions
in an Angular expression.
* **No Comma And Void Operators:** You cannot use `,` or `void` in an Angular expression.
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
@@ -175,7 +175,7 @@ expression, delegate to a JavaScript method instead.
## No function declarations or RegExp creation with literal notation
You can't declare functions or create regular expressions from within AngularJS expressions. This is
to avoid complex model transformation logic inside templates. Such logic is better placed in a
to avoid complex model transformation logic inside templates. Such logic is better placed in a
controller or in a dedicated filter where it can be tested properly.
## `$event`
@@ -303,19 +303,23 @@ then the expression is not fulfilled and will remain watched.
keep dirty-checking the watch in the future digest loops by following the same
algorithm starting from step 1
#### Special case for object literals
Unlike simple values, object-literals are watched until every key is defined.
See http://www.bennadel.com/blog/2760-one-time-data-bindings-for-object-literal-expressions-in-angularjs-1-3.htm
### How to benefit from one-time binding
If the expression will not change once set, it is a candidate for one-time binding.
If the expression will not change once set, it is a candidate for one-time binding.
Here are three example cases.
When interpolating text or attributes:
```html
<div name="attr: {{::color}}">text: {{::name}}</div>
<div name="attr: {{::color}}">text: {{::name | uppercase}}</div>
```
When using a directive with bidirectional binding and the parameters will not change:
When using a directive with bidirectional binding and parameters that will not change:
```js
someModule.directive('someDirective', function() {
@@ -338,7 +342,6 @@ When using a directive that takes an expression:
```html
<ul>
<li ng-repeat="item in ::items">{{item.name}};</li>
<li ng-repeat="item in ::items | orderBy:'name'">{{item.name}};</li>
</ul>
```
```
+1 -1
View File
@@ -305,7 +305,7 @@ matching is **case-sensitive**.
#### Selection Keywords
Selection keywords are simple words like "male" and "female". The keyword, "other", and it's
Selection keywords are simple words like "male" and "female". The keyword, "other", and its
corresponding message are required while others are optional. It is used when the Angular
expression does not match (case-insensitively) any of the other keywords specified.
+33 -4
View File
@@ -43,7 +43,7 @@ Animations in 1.4 have been refactored internally, but the API has stayed much t
some breaking changes that need to be addressed when upgrading to 1.4.
Due to [c8700f04](https://github.com/angular/angular.js/commit/c8700f04fb6fb5dc21ac24de8665c0476d6db5ef),
JavaSript and CSS animations can no longer be run in
JavaScript 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 been removed, however, the same effect, with even more
@@ -136,7 +136,7 @@ class based animations (animations triggered via ngClass) in order to ensure tha
## Forms (`ngMessages`, `ngOptions`)
## Forms (`ngMessages`, `ngOptions`, `select`)
### ngMessages
The ngMessages module has also been subject to an internal refactor to allow it to be more flexible
@@ -178,8 +178,8 @@ have been fixed. The breaking changes are comparatively minor and should not aff
Due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
when `ngOptions` renders the option values within the DOM, the resulting HTML code is different.
Normally this should not affect your application at all, however, if your code relies on inspecting
the value property of `<option>` elements (that `ngOptions` generates) then be sure to [read the details]
(https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef).
the value property of `<option>` elements (that `ngOptions` generates) then be sure
to [read the details](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef).
Due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
when iterating over an object's properties using the `(key, value) in obj` syntax
@@ -190,6 +190,35 @@ in the order they are returned by Object.keys(obj), which is almost always the o
in which the properties were defined.
### select
Due to [7fda214c](https://github.com/angular/angular.js/commit/7fda214c4f65a6a06b25cf5d5aff013a364e9cef),
the `select` directive will now use strict comparison of the `ngModel` scope value against `option`
values to determine which option is selected. This means `Number` scope values will not be matched
against numeric option strings.
In Angular 1.3.x, setting `scope.x = 200` would select the option with the value 200 in the following `select`:
```
<select ng-model="x">
<option value="100">100</option>
<option value="200">200</option>
</select>
```
In Angular 1.4.x, the 'unknown option' will be selected.
To remedy this, you can simply initialize the model as a string: `scope.x = '200'`, or if you want to
keep the model as a `Number`, you can do the conversion via `$formatters` and `$parsers` on `ngModel`:
```js
ngModelCtrl.$parsers.push(function(value) {
return parseInt(value, 10); // Convert option value to number
});
ngModelCtrl.$formatters.push(function(value) {
return value.toString(); // Convert scope value to string
});
```
## Templating (`ngRepeat`, `$compile`)
### ngRepeat
+1 -1
View File
@@ -140,7 +140,7 @@ The above is a suggestion. Tailor it to your needs.
# Module Loading & Dependencies
A module is a collection of configuration and run blocks which get applied to the application
during the bootstrap process. In its simplest form the module consist of a collection of two kinds
during the bootstrap process. In its simplest form the module consists of a collection of two kinds
of blocks:
1. **Configuration blocks** - get executed during the provider registrations and configuration
+1 -1
View File
@@ -5,7 +5,7 @@
# What are Scopes?
{@link ng.$rootScope.Scope scope} is an object that refers to the application
{@link ng.$rootScope.Scope Scope} is an object that refers to the application
model. It is an execution context for {@link expression expressions}. Scopes are
arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can
watch {@link guide/expression expressions} and propagate events.
+1 -1
View File
@@ -39,7 +39,7 @@ In general, we recommend against this because it can create unintended XSS vecto
However, it's ok to mix server-side templating in the bootstrap template (`index.html`) as long
as user input cannot be used on the server to output html that would then be processed by Angular
in a way that would cause allow for arbitrary code execution.
in a way that would allow for arbitrary code execution.
For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
for generating templates that are bootstrapped/compiled by Angular.
+1 -1
View File
@@ -194,7 +194,7 @@ Conditionally showing and hiding things using jQuery is a common pattern in othe
`ng-show` (and `ng-hide`) conditionally show and hide elements based on boolean expressions.
Describe the conditions for showing and hiding an element in terms of `$scope` variables:
<div ng-show="!loggedIn">Click <a href="#/login">here</a> to log in</div>
<div ng-show="!loggedIn"><a href="#/login">Click here to log in</a></div>
Note also the counterpart `ng-hide` and similar `ng-disabled`.
Note especially the powerful `ng-switch` that should be used instead of several mutually exclusive `ng-show`s.
+3 -1
View File
@@ -129,7 +129,8 @@ Once you have Node.js installed on your machine you can download the tool depend
npm install
```
This command will download the following tools, into the `node_modules` directory:
This command reads angular-phonecat's `package.json` file and downloads the following tools
into the `node_modules` directory:
- [Bower][bower] - client-side code package manager
- [Http-Server][http-server] - simple local static web server
@@ -270,6 +271,7 @@ It is good to run the end to end tests whenever you make changes to the HTML vie
that the application as a whole is executing correctly. It is very common to run End to End tests
before pushing a new commit of changes to a remote repository.
Now that you have set up your local machine, let's get started with the tutorial: {@link step_00 Step 0 - Bootstrapping}
[git]: http://git-scm.com/
[node]: http://nodejs.org/
+13 -13
View File
@@ -31,7 +31,7 @@ npm install
To see the app running in a browser, open a *separate* terminal/command line tab or window, then
run `npm start` to start the web server. Now, open a browser window for the app and navigate to
<a href="http://localhost:8000/app/" target="_blank">`http://localhost:8000/app/`</a>
<a href="http://localhost:8000/app/" target="_blank" title="Open app on localhost">`http://localhost:8000/app/`</a>
Note that if you already ran the master branch app prior to checking out step-0, you may see the cached
master version of the app in your browser window at this point. Just hit refresh to re-load the page.
@@ -91,22 +91,22 @@ being the element on which the `ngApp` directive was defined.
Nothing here {{'yet' + '!'}}
This line demonstrates two core features of Angular's templating capabilities:
This line demonstrates two core features of Angular's templating capabilities:
* a binding, denoted by double-curlies `{{ }}`
* a simple expression `'yet' + '!'` used in this binding.
* a binding, denoted by double-curlies `{{ }}`
* a simple expression `'yet' + '!'` used in this binding.
The binding tells Angular that it should evaluate an expression and insert the result into the
DOM in place of the binding. Rather than a one-time insert, as we'll see in the next steps, a
binding will result in efficient continuous updates whenever the result of the expression
evaluation changes.
The binding tells Angular that it should evaluate an expression and insert the result into the
DOM in place of the binding. Rather than a one-time insert, as we'll see in the next steps, a
binding will result in efficient continuous updates whenever the result of the expression
evaluation changes.
{@link guide/expression Angular expression} is a JavaScript-like code snippet that is
evaluated by Angular in the context of the current model scope, rather than within the scope of
the global context (`window`).
{@link guide/expression Angular expression} is a JavaScript-like code snippet that is
evaluated by Angular in the context of the current model scope, rather than within the scope of
the global context (`window`).
As expected, once this template is processed by Angular, the html page contains the text:
"Nothing here yet!".
As expected, once this template is processed by Angular, the html page contains the text:
"Nothing here yet!".
## Bootstrapping AngularJS apps
+1
View File
@@ -1,5 +1,6 @@
{
"name": "angularjs",
"license": "MIT",
"branchVersion": "^1.4.0-beta.0",
"branchPattern": "1.4.*",
"repository": {
+22 -26
View File
@@ -795,9 +795,18 @@ function copy(source, destination, stackSource, stackDest) {
if (!destination) {
destination = source;
if (source) {
if (isObject(source)) {
var index;
if (stackSource && (index = stackSource.indexOf(source)) !== -1) {
return stackDest[index];
}
// TypedArray, Date and RegExp have specific copy functionality and must be
// pushed onto the stack before returning.
// Array and other objects create the base object and recurse to copy child
// objects. The array/object will be pushed onto the stack when recursed.
if (isArray(source)) {
destination = copy(source, [], stackSource, stackDest);
return copy(source, [], stackSource, stackDest);
} else if (isTypedArray(source)) {
destination = new source.constructor(source);
} else if (isDate(source)) {
@@ -805,9 +814,14 @@ function copy(source, destination, stackSource, stackDest) {
} else if (isRegExp(source)) {
destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
destination.lastIndex = source.lastIndex;
} else if (isObject(source)) {
} else {
var emptyObject = Object.create(getPrototypeOf(source));
destination = copy(source, emptyObject, stackSource, stackDest);
return copy(source, emptyObject, stackSource, stackDest);
}
if (stackDest) {
stackSource.push(source);
stackDest.push(destination);
}
}
} else {
@@ -818,9 +832,6 @@ function copy(source, destination, stackSource, stackDest) {
stackDest = stackDest || [];
if (isObject(source)) {
var index = stackSource.indexOf(source);
if (index !== -1) return stackDest[index];
stackSource.push(source);
stackDest.push(destination);
}
@@ -829,12 +840,7 @@ function copy(source, destination, stackSource, stackDest) {
if (isArray(source)) {
destination.length = 0;
for (var i = 0; i < source.length; i++) {
result = copy(source[i], null, stackSource, stackDest);
if (isObject(source[i])) {
stackSource.push(source[i]);
stackDest.push(result);
}
destination.push(result);
destination.push(copy(source[i], null, stackSource, stackDest));
}
} else {
var h = destination.$$hashKey;
@@ -848,20 +854,20 @@ function copy(source, destination, stackSource, stackDest) {
if (isBlankObject(source)) {
// createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty
for (key in source) {
putValue(key, source[key], destination, stackSource, stackDest);
destination[key] = copy(source[key], null, stackSource, stackDest);
}
} else if (source && typeof source.hasOwnProperty === 'function') {
// Slow path, which must rely on hasOwnProperty
for (key in source) {
if (source.hasOwnProperty(key)) {
putValue(key, source[key], destination, stackSource, stackDest);
destination[key] = copy(source[key], null, stackSource, stackDest);
}
}
} else {
// Slowest path --- hasOwnProperty can't be called as a method
for (key in source) {
if (hasOwnProperty.call(source, key)) {
putValue(key, source[key], destination, stackSource, stackDest);
destination[key] = copy(source[key], null, stackSource, stackDest);
}
}
}
@@ -869,16 +875,6 @@ function copy(source, destination, stackSource, stackDest) {
}
}
return destination;
function putValue(key, val, destination, stackSource, stackDest) {
// No context allocation, trivial outer scope, easily inlined
var result = copy(val, null, stackSource, stackDest);
if (isObject(val)) {
stackSource.push(val);
stackDest.push(result);
}
destination[key] = result;
}
}
/**
+1 -1
View File
@@ -645,7 +645,7 @@ function createInjector(modulesToLoad, strictDi) {
}));
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
return instanceInjector;
+9 -1
View File
@@ -182,6 +182,13 @@ function jqLiteAcceptsData(node) {
return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT;
}
function jqLiteHasData(node) {
for (var key in jqCache[node.ng339]) {
return true;
}
return false;
}
function jqLiteBuildFragment(html, context) {
var tmp, tag, wrap,
fragment = context.createDocumentFragment(),
@@ -556,7 +563,8 @@ function getAliasedAttrName(element, name) {
forEach({
data: jqLiteData,
removeData: jqLiteRemoveData
removeData: jqLiteRemoveData,
hasData: jqLiteHasData
}, function(fn, name) {
JQLite[name] = fn;
});
+21 -8
View File
@@ -146,7 +146,7 @@ function setupModuleLoader(window) {
* @description
* See {@link auto.$provide#provider $provide.provider()}.
*/
provider: invokeLater('$provide', 'provider'),
provider: invokeLaterAndSetModuleName('$provide', 'provider'),
/**
* @ngdoc method
@@ -157,7 +157,7 @@ function setupModuleLoader(window) {
* @description
* See {@link auto.$provide#factory $provide.factory()}.
*/
factory: invokeLater('$provide', 'factory'),
factory: invokeLaterAndSetModuleName('$provide', 'factory'),
/**
* @ngdoc method
@@ -168,7 +168,7 @@ function setupModuleLoader(window) {
* @description
* See {@link auto.$provide#service $provide.service()}.
*/
service: invokeLater('$provide', 'service'),
service: invokeLaterAndSetModuleName('$provide', 'service'),
/**
* @ngdoc method
@@ -203,7 +203,7 @@ function setupModuleLoader(window) {
* @description
* See {@link auto.$provide#decorator $provide.decorator()}.
*/
decorator: invokeLater('$provide', 'decorator'),
decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
/**
* @ngdoc method
@@ -237,7 +237,7 @@ function setupModuleLoader(window) {
* See {@link ng.$animateProvider#register $animateProvider.register()} and
* {@link ngAnimate ngAnimate module} for more information.
*/
animation: invokeLater('$animateProvider', 'register'),
animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
/**
* @ngdoc method
@@ -255,7 +255,7 @@ function setupModuleLoader(window) {
* (`myapp_subsection_filterx`).
* </div>
*/
filter: invokeLater('$filterProvider', 'register'),
filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
/**
* @ngdoc method
@@ -267,7 +267,7 @@ function setupModuleLoader(window) {
* @description
* See {@link ng.$controllerProvider#register $controllerProvider.register()}.
*/
controller: invokeLater('$controllerProvider', 'register'),
controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
/**
* @ngdoc method
@@ -280,7 +280,7 @@ function setupModuleLoader(window) {
* @description
* See {@link ng.$compileProvider#directive $compileProvider.directive()}.
*/
directive: invokeLater('$compileProvider', 'directive'),
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
/**
* @ngdoc method
@@ -330,6 +330,19 @@ function setupModuleLoader(window) {
return moduleInstance;
};
}
/**
* @param {string} provider
* @param {string} method
* @returns {angular.Module}
*/
function invokeLaterAndSetModuleName(provider, method) {
return function(recipeName, factoryFunction) {
if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
invokeQueue.push([provider, method, arguments]);
return moduleInstance;
};
}
});
};
});
+1 -1
View File
@@ -147,7 +147,7 @@ function Browser(window, document, $log, $sniffer) {
// Do the assignment again so that those two variables are referentially identical.
lastHistoryState = cachedState;
} else {
if (!sameBase) {
if (!sameBase || reloadLocation) {
reloadLocation = url;
}
if (replace) {
+60 -31
View File
@@ -399,13 +399,16 @@
* * `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:
* * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one
* * `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.
*
* Note that you can also require the directive's own controller - it will be made available like
* like any other controller.
*
* * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
* This is the same as the `$transclude`
* parameter of directive controllers, see there for details.
@@ -852,6 +855,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (isObject(bindings.isolateScope)) {
directive.$$isolateBindings = bindings.isolateScope;
}
directive.$$moduleName = directiveFactory.$$moduleName;
directives.push(directive);
} catch (e) {
$exceptionHandler(e);
@@ -1423,8 +1427,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (nodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(
scope, nodeLinkFn.transclude, parentBoundTranscludeFn,
nodeLinkFn.elementTranscludeOnThisElement);
scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
} else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
childBoundTranscludeFn = parentBoundTranscludeFn;
@@ -1446,7 +1449,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) {
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) {
@@ -1545,6 +1548,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
break;
case NODE_TYPE_TEXT: /* Text Node */
if (msie === 11) {
// Workaround for #11781
while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) {
node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
node.parentNode.removeChild(node.nextSibling);
}
}
addTextInterpolateDirective(directives, node.nodeValue);
break;
case NODE_TYPE_COMMENT: /* Comment */
@@ -1837,7 +1847,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective;
nodeLinkFn.templateOnThisElement = hasTemplate;
nodeLinkFn.transclude = childTranscludeFn;
@@ -1998,9 +2007,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
for (i in elementControllers) {
controller = elementControllers[i];
var controllerResult = controller();
if (controllerResult !== controller.instance) {
// If the controller constructor has a return value, overwrite the instance
// from setupControllers and update the element data
controller.instance = controllerResult;
$element.data('$' + directive.name + 'Controller', controllerResult);
$element.data('$' + i + 'Controller', controllerResult);
if (controller === controllerForBindings) {
// Remove and re-install bindToController bindings
thisLinkFn.$$destroyBindings();
@@ -2300,11 +2312,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return a.index - b.index;
}
function assertNoDuplicate(what, previousDirective, directive, element) {
function wrapModuleNameIfDefined(moduleName) {
return moduleName ?
(' (module: ' + moduleName + ')') :
'';
}
if (previousDirective) {
throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}',
previousDirective.name, directive.name, what, startingTag(element));
throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}',
previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName),
directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element));
}
}
@@ -2485,26 +2504,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var fragment = document.createDocumentFragment();
fragment.appendChild(firstElementToRemove);
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
// data here because there's no public interface in jQuery to do that and copying over
// event listeners (which is the main use of private data) wouldn't work anyway.
jqLite(newNode).data(jqLite(firstElementToRemove).data());
if (jqLite.hasData(firstElementToRemove)) {
// Copy over user data (that includes Angular's $scope etc.). Don't copy private
// data here because there's no public interface in jQuery to do that and copying over
// event listeners (which is the main use of private data) wouldn't work anyway.
jqLite(newNode).data(jqLite(firstElementToRemove).data());
// Remove data of the replaced element. We cannot just call .remove()
// on the element it since that would deallocate scope that is needed
// for the new node. Instead, remove the data "manually".
if (!jQuery) {
delete jqLite.cache[firstElementToRemove[jqLite.expando]];
} else {
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
// the replaced element. The cleanData version monkey-patched by Angular would cause
// the scope to be trashed and we do need the very same scope to work with the new
// element. However, we cannot just cache the non-patched version and use it here as
// that would break if another library patches the method after Angular does (one
// example is jQuery UI). Instead, set a flag indicating scope destroying should be
// skipped this one time.
skipDestroyOnNextJQueryCleanData = true;
jQuery.cleanData([firstElementToRemove]);
// Remove data of the replaced element. We cannot just call .remove()
// on the element it since that would deallocate scope that is needed
// for the new node. Instead, remove the data "manually".
if (!jQuery) {
delete jqLite.cache[firstElementToRemove[jqLite.expando]];
} else {
// jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after
// the replaced element. The cleanData version monkey-patched by Angular would cause
// the scope to be trashed and we do need the very same scope to work with the new
// element. However, we cannot just cache the non-patched version and use it here as
// that would break if another library patches the method after Angular does (one
// example is jQuery UI). Instead, set a flag indicating scope destroying should be
// skipped this one time.
skipDestroyOnNextJQueryCleanData = true;
jQuery.cleanData([firstElementToRemove]);
}
}
for (var k = 1, kk = elementsToRemove.length; k < kk; k++) {
@@ -2545,9 +2566,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
lastValue,
parentGet, parentSet, compare;
if (!hasOwnProperty.call(attrs, attrName)) {
// In the case of user defined a binding with the same name as a method in Object.prototype but didn't set
// the corresponding attribute. We need to make sure subsequent code won't access to the prototype function
attrs[attrName] = undefined;
}
switch (mode) {
case '@':
if (!attrs[attrName] && !optional) {
destination[scopeName] = undefined;
}
attrs.$observe(attrName, function(value) {
destination[scopeName] = value;
});
@@ -2564,6 +2595,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return;
}
parentGet = $parse(attrs[attrName]);
if (parentGet.literal) {
compare = equals;
} else {
@@ -2602,9 +2634,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
break;
case '&':
// Don't assign Object.prototype method to scope
if (!attrs.hasOwnProperty(attrName) && optional) break;
parentGet = $parse(attrs[attrName]);
// Don't assign noop to destination if expression is not valid
+11 -1
View File
@@ -13,7 +13,7 @@
var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/;
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/;
var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/;
var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
@@ -612,6 +612,16 @@ var inputType = {
* error docs for more information and an example of how to convert your model if necessary.
* </div>
*
* ## Issues with HTML5 constraint validation
*
* In browsers that follow the
* [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29),
* `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}.
* If a non-number is entered in the input, the browser will report the value as an empty string,
* which means the view / model values in `ngModel` and subsequently the scope value
* will also be an empty string.
*
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} min Sets the `min` validation error key if the value entered is less than `min`.
+8 -4
View File
@@ -162,7 +162,7 @@ function classDirective(name, selector) {
* @example Example that demonstrates basic bindings via ngClass directive.
<example>
<file name="index.html">
<p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p>
<p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p>
<label>
<input type="checkbox" ng-model="deleted">
deleted (apply "strike" class)
@@ -173,7 +173,7 @@ function classDirective(name, selector) {
</label><br>
<label>
<input type="checkbox" ng-model="error">
error (apply "red" class)
error (apply "has-error" class)
</label>
<hr>
<p ng-class="style">Using String Syntax</p>
@@ -202,6 +202,10 @@ function classDirective(name, selector) {
.red {
color: red;
}
.has-error {
color: red;
background-color: yellow;
}
.orange {
color: orange;
}
@@ -212,13 +216,13 @@ function classDirective(name, selector) {
it('should let you toggle the class', function() {
expect(ps.first().getAttribute('class')).not.toMatch(/bold/);
expect(ps.first().getAttribute('class')).not.toMatch(/red/);
expect(ps.first().getAttribute('class')).not.toMatch(/has-error/);
element(by.model('important')).click();
expect(ps.first().getAttribute('class')).toMatch(/bold/);
element(by.model('error')).click();
expect(ps.first().getAttribute('class')).toMatch(/red/);
expect(ps.first().getAttribute('class')).toMatch(/has-error/);
});
it('should let you toggle string example', function() {
+4 -2
View File
@@ -1101,7 +1101,7 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
* - `debounce`: integer value which contains the debounce model update value in milliseconds. A
* value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
* custom value for each event. For example:
* `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"`
* `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"`
* - `allowInvalid`: boolean value which indicates that the model can be set with values that did
* not validate correctly instead of the default behavior of setting the model to undefined.
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
@@ -1351,7 +1351,9 @@ function addSetValidityMethod(context) {
function isObjectEmpty(obj) {
if (obj) {
for (var prop in obj) {
return false;
if (obj.hasOwnProperty(prop)) {
return false;
}
}
}
return true;
+2 -2
View File
@@ -303,6 +303,7 @@ var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) {
values = values || [];
Object.keys(values).forEach(function getWatchable(key) {
if (key.charAt(0) === '$') return;
var locals = getLocals(values[key], key);
var selectValue = getTrackByValueFn(values[key], locals);
watchedArray.push(selectValue);
@@ -706,8 +707,7 @@ 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 (ngOptions.trackBy && !equals(previousValue, nextValue) ||
previousValue !== nextValue) {
if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) {
ngModelCtrl.$setViewValue(nextValue);
ngModelCtrl.$render();
}
+12 -2
View File
@@ -97,6 +97,15 @@
* </div>
* ```
*
* <div class="alert alert-warning">
* **Note:** `track by` must always be the last expression:
* </div>
* ```
* <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
* {{model.name}}
* </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.
@@ -168,8 +177,9 @@
* 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.
* mapped to the same DOM element, which is not possible.)
*
* Note that the tracking expression must come last, after any filters, and the alias 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.
+5 -3
View File
@@ -34,9 +34,11 @@
* `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
* **will** be matched by `{$: 'John'}`.
*
* - `function(value, index)`: A predicate function can be used to write arbitrary filters. The
* function is called for each element of `array`. The final result is an array of those
* elements that the predicate returned true for.
* - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
* The function is called for each element of the array, with the element, its index, and
* the entire array itself as arguments.
*
* The final result is an array of those elements that the predicate returned true for.
*
* @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
* determining if the expected value (from the filter expression) and actual value (from
+2 -1
View File
@@ -80,9 +80,10 @@ function currencyFilter($locale) {
* @description
* Formats a number as text.
*
* If the input is null or undefined, it will just be returned.
* If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
* If the input is not a number an empty string is returned.
*
* If the input is an infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
*
* @param {number|string} number Number to format.
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
+27 -6
View File
@@ -8,7 +8,7 @@
* @description
* Orders a specified `array` by the `expression` predicate. It is ordered alphabetically
* for strings and numerically for numbers. Note: if you notice numbers are not being sorted
* correctly, make sure they are actually being saved as numbers and not strings.
* as expected, make sure they are actually being saved as numbers and not strings.
*
* @param {Array} array The array to sort.
* @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
@@ -83,19 +83,40 @@
{name:'Mike', phone:'555-4321', age:21},
{name:'Adam', phone:'555-5678', age:35},
{name:'Julie', phone:'555-8765', age:29}];
$scope.predicate = '-age';
$scope.predicate = 'age';
$scope.reverse = true;
$scope.order = function(predicate) {
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.predicate = predicate;
};
}]);
</script>
<style type="text/css">
.sortorder:after {
content: '\25b2';
}
.sortorder.reverse:after {
content: '\25bc';
}
</style>
<div ng-controller="ExampleController">
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
<hr/>
[ <a href="" ng-click="predicate=''">unsorted</a> ]
<table class="friend">
<tr>
<th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a>
(<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th>
<th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
<th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('phone')">Phone Number</a>
<span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('age')">Age</a>
<span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span>
</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:predicate:reverse">
<td>{{friend.name}}</td>
+60 -13
View File
@@ -23,13 +23,17 @@ function $HttpParamSerializerProvider() {
* @name $httpParamSerializer
* @description
*
* Default $http params serializer that converts objects to a part of a request URL
* Default {@link $http `$http`} params serializer that converts objects to strings
* 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)
*
* Note that serializer will sort the request parameters alphabetically.
* */
this.$get = function() {
return function ngParamSerializer(params) {
if (!params) return '';
@@ -56,7 +60,43 @@ function $HttpParamSerializerJQLikeProvider() {
* @name $httpParamSerializerJQLike
* @description
*
* Alternative $http params serializer that follows jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
* Alternative {@link $http `$http`} params serializer that follows
* jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
* The serializer will also sort the params alphabetically.
*
* To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
*
* ```js
* $http({
* url: myUrl,
* method: 'GET',
* params: myParams,
* paramSerializer: '$httpParamSerializerJQLike'
* });
* ```
*
* It is also possible to set it as the default `paramSerializer` in the
* {@link $httpProvider#defaults `$httpProvider`}.
*
* Additionally, you can inject the serializer and use it explicitly, for example to serialize
* form data for submission:
*
* ```js
* .controller(function($http, $httpParamSerializerJQLike) {
* //...
*
* $http({
* url: myUrl,
* method: 'POST',
* data: $httpParamSerializerJQLike(myData),
* headers: {
* 'Content-Type': 'application/x-www-form-urlencoded'
* }
* });
*
* });
* ```
*
* */
this.$get = function() {
return function jQueryLikeParamSerializer(params) {
@@ -230,10 +270,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).
* If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
* Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
*
* - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
* used to the prepare string representation of request parameters (specified as an object).
* If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
* Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
*
**/
var defaults = this.defaults = {
@@ -699,15 +740,17 @@ function $HttpProvider() {
* properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
* or the per-request config object.
*
* In order to prevent collisions in environments where multiple Angular apps share the
* same domain or subdomain, we recommend that each application uses unique cookie name.
*
*
* @param {object} config Object describing the request to be made and how it should be
* processed. The object has following properties:
*
* - **method** `{string}` HTTP method (e.g. 'GET', 'POST', etc)
* - **url** `{string}` Absolute or relative URL of the resource that is being requested.
* - **params** `{Object.<string|Object>}` Map of strings or objects which will be turned
* to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be
* JSONified.
* - **params** `{Object.<string|Object>}` Map of strings or objects which will be serialized
* with the `paramSerializer` and appended as GET parameters.
* - **data** `{string|Object}` Data to be sent as the request message data.
* - **headers** `{Object}` Map of strings or functions which return strings representing
* HTTP headers to send to the server. If the return value of a function is null, the
@@ -725,10 +768,14 @@ function $HttpProvider() {
* transform function or an array of such functions. The transform function takes the http
* 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}.
* Overriding the Default TransformationjqLiks}
* - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
* prepare the string representation of request parameters (specified as an object).
* If specified as string, it is interpreted as function registered with the
* {@link $injector $injector}, which means you can create your own serializer
* by registering it as a {@link auto.$provide#service service}.
* The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
* alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
* - **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
+6 -2
View File
@@ -982,8 +982,10 @@ ASTCompiler.prototype = {
nameId.name = ast.property.name;
}
}
recursionFn(intoId);
}, function() {
self.assign(intoId, 'undefined');
});
recursionFn(intoId);
}, !!create);
break;
case AST.CallExpression:
@@ -1021,8 +1023,10 @@ ASTCompiler.prototype = {
}
expression = self.ensureSafeObject(expression);
self.assign(intoId, expression);
recursionFn(intoId);
}, function() {
self.assign(intoId, 'undefined');
});
recursionFn(intoId);
});
}
break;
+14
View File
@@ -497,6 +497,19 @@ function qFactory(nextTick, exceptionHandler) {
return result.promise.then(callback, errback, progressBack);
};
/**
* @ngdoc method
* @name $q#resolve
* @kind function
*
* @description
* Alias of {@link ng.$q#when when} to maintain naming consistency with ES6.
*
* @param {*} value Value or a promise
* @returns {Promise} Returns a promise of the passed value or promise
*/
var resolve = when;
/**
* @ngdoc method
* @name $q#all
@@ -565,6 +578,7 @@ function qFactory(nextTick, exceptionHandler) {
$Q.defer = defer;
$Q.reject = reject;
$Q.when = when;
$Q.resolve = resolve;
$Q.all = all;
return $Q;
+2 -1
View File
@@ -176,7 +176,8 @@
* unless you really need a digest to kick off afterwards.
*
* Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
* (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). Check the [animation code above](#usage) to see how this works.
* (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
* Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
*
* @param {DOMElement} element the element that will be animated
* @param {object} options the animation-related options that will be applied during the animation
+1 -1
View File
@@ -72,7 +72,7 @@
* opacity:0;
* }
*
* /&#42; The starting CSS styles for the enter animation &#42;/
* /&#42; The finishing CSS styles for the enter animation &#42;/
* .fade.ng-enter.ng-enter-active {
* opacity:1;
* }
+19 -10
View File
@@ -14,8 +14,8 @@
*
* ## Usage
*
* For ngAria to do its magic, simply include the module as a dependency. The directives supported
* by ngAria are:
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
* directives are supported:
* `ngModel`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, `ngDblClick`, and `ngMessages`.
*
* Below is a more detailed breakdown of the attributes handled by ngAria:
@@ -115,9 +115,8 @@ function $AriaProvider() {
var ariaCamelName = attr.$normalize(ariaAttr);
if (config[ariaCamelName] && !attr[ariaCamelName]) {
scope.$watch(attr[attrName], function(boolVal) {
if (negate) {
boolVal = !boolVal;
}
// ensure boolean value
boolVal = negate ? !boolVal : !!boolVal;
elem.attr(ariaAttr, boolVal);
});
}
@@ -265,13 +264,23 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
elem.attr('role', 'slider');
}
if ($aria.config('ariaValue')) {
if (attr.min && !elem.attr('aria-valuemin')) {
elem.attr('aria-valuemin', attr.min);
var needsAriaValuemin = !elem.attr('aria-valuemin') &&
(attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin'));
var needsAriaValuemax = !elem.attr('aria-valuemax') &&
(attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax'));
var needsAriaValuenow = !elem.attr('aria-valuenow');
if (needsAriaValuemin) {
attr.$observe('min', function ngAriaValueMinReaction(newVal) {
elem.attr('aria-valuemin', newVal);
});
}
if (attr.max && !elem.attr('aria-valuemax')) {
elem.attr('aria-valuemax', attr.max);
if (needsAriaValuemax) {
attr.$observe('max', function ngAriaValueMinReaction(newVal) {
elem.attr('aria-valuemax', newVal);
});
}
if (!elem.attr('aria-valuenow')) {
if (needsAriaValuenow) {
scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
elem.attr('aria-valuenow', newVal);
});
+1 -1
View File
@@ -43,7 +43,7 @@ angular.module('ngCookies', ['ng']).
* or a Date object indicating the exact date/time this cookie will expire.
* - **secure** - `{boolean}` - The cookie will be available only in secured connection.
*
* Note: by default the address that appears in your <base> tag will be used as path.
* Note: by default the address that appears in your `<base>` tag will be used as path.
* This is import so that cookies will be visible for all routes in case html5mode is enabled
*
**/
+2 -2
View File
@@ -51,7 +51,7 @@ var jqLite = angular.element;
*
* ```javascript
* <!-- keep in mind that ngModel automatically sets these error flags -->
* myField.$error = { minlength : true, required : false };
* myField.$error = { minlength : true, required : true };
* ```
*
* Then the `required` message will be displayed first. When required is false then the `minlength` message
@@ -251,7 +251,7 @@ angular.module('ngMessages', [])
*
* @description
* `ngMessages` is a directive that is designed to show and hide messages based on the state
* of a key/value object that it listens on. The directive itself compliments error message
* of a key/value object that it listens on. The directive itself complements error message
* reporting with the `ngModel` $error object (which stores a key/value state of validation errors).
*
* `ngMessages` manages the state of internal messages within its container element. The internal
+1 -1
View File
@@ -1083,7 +1083,7 @@ angular.mock.dump = function(object) {
$httpBackend.flush();
$httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
// check if the header was send, if it wasn't the expectation won't
// check if the header was sent, if it wasn't the expectation won't
// match the request and the test will fail
return headers['Authorization'] == 'xxx';
}).respond(201, '');
+2 -2
View File
@@ -104,8 +104,8 @@
*/
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,
MAILTO_REGEXP = /^mailto:/;
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/i,
MAILTO_REGEXP = /^mailto:/i;
return function(text, target) {
if (!text) return text;
+1 -1
View File
@@ -228,7 +228,7 @@ var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
'valign,value,vspace,width');
// SVG attributes (without "id" and "name" attributes)
+63 -2
View File
@@ -336,7 +336,7 @@ describe('angular', function() {
expect(hashKey(dst)).not.toEqual(hashKey(src));
});
it('should retain the previous $$hashKey', function() {
it('should retain the previous $$hashKey when copying object with hashKey', function() {
var src,dst,h;
src = {};
dst = {};
@@ -351,7 +351,21 @@ describe('angular', function() {
expect(hashKey(dst)).toEqual(h);
});
it('should handle circular references when circularRefs is turned on', function() {
it('should retain the previous $$hashKey when copying non-object', function() {
var dst = {};
var h = hashKey(dst);
copy(null, dst);
expect(hashKey(dst)).toEqual(h);
copy(42, dst);
expect(hashKey(dst)).toEqual(h);
copy(new Date(), dst);
expect(hashKey(dst)).toEqual(h);
});
it('should handle circular references', function() {
var a = {b: {a: null}, self: null, selfs: [null, null, [null]]};
a.b.a = a;
a.self = a;
@@ -362,13 +376,60 @@ describe('angular', function() {
expect(aCopy).not.toBe(a);
expect(aCopy).toBe(aCopy.self);
expect(aCopy).toBe(aCopy.selfs[2][0]);
expect(aCopy.selfs[2]).not.toBe(a.selfs[2]);
var copyTo = [];
aCopy = copy(a, copyTo);
expect(aCopy).toBe(copyTo);
expect(aCopy).not.toBe(a);
expect(aCopy).toBe(aCopy.self);
});
it('should handle objects with multiple references', function() {
var b = {};
var a = [b, -1, b];
var aCopy = copy(a);
expect(aCopy[0]).not.toBe(a[0]);
expect(aCopy[0]).toBe(aCopy[2]);
var copyTo = [];
aCopy = copy(a, copyTo);
expect(aCopy).toBe(copyTo);
expect(aCopy[0]).not.toBe(a[0]);
expect(aCopy[0]).toBe(aCopy[2]);
});
it('should handle date/regex objects with multiple references', function() {
var re = /foo/;
var d = new Date();
var o = {re: re, re2: re, d: d, d2: d};
var oCopy = copy(o);
expect(oCopy.re).toBe(oCopy.re2);
expect(oCopy.d).toBe(oCopy.d2);
oCopy = copy(o, {});
expect(oCopy.re).toBe(oCopy.re2);
expect(oCopy.d).toBe(oCopy.d2);
});
it('should clear destination arrays correctly when source is non-array', function() {
expect(copy(null, [1,2,3])).toEqual([]);
expect(copy(undefined, [1,2,3])).toEqual([]);
expect(copy({0: 1, 1: 2}, [1,2,3])).toEqual([1,2]);
expect(copy(new Date(), [1,2,3])).toEqual([]);
expect(copy(/a/, [1,2,3])).toEqual([]);
expect(copy(true, [1,2,3])).toEqual([]);
});
it('should clear destination objects correctly when source is non-array', function() {
expect(copy(null, {0:1,1:2,2:3})).toEqual({});
expect(copy(undefined, {0:1,1:2,2:3})).toEqual({});
expect(copy(new Date(), {0:1,1:2,2:3})).toEqual({});
expect(copy(/a/, {0:1,1:2,2:3})).toEqual({});
expect(copy(true, {0:1,1:2,2:3})).toEqual({});
});
it('should copy objects with no prototype parent', function() {
+6
View File
@@ -405,10 +405,13 @@ describe('jqLite', function() {
it('should provide the non-wrapped data calls', function() {
var node = document.createElement('div');
expect(jqLite.hasData(node)).toBe(false);
expect(jqLite.data(node, "foo")).toBeUndefined();
expect(jqLite.hasData(node)).toBe(false);
jqLite.data(node, "foo", "bar");
expect(jqLite.hasData(node)).toBe(true);
expect(jqLite.data(node, "foo")).toBe("bar");
expect(jqLite(node).data("foo")).toBe("bar");
@@ -421,6 +424,9 @@ describe('jqLite', function() {
jqLite.removeData(node);
jqLite.removeData(node);
expect(jqLite.data(node, "bar")).toBeUndefined();
jqLite(node).remove();
expect(jqLite.hasData(node)).toBe(false);
});
it('should emit $destroy event if element removed via remove()', function() {
+275 -3
View File
@@ -2231,6 +2231,45 @@ describe('$compile', function() {
}}
};
});
directive('prototypeMethodNameAsScopeVarA', function() {
return {
scope: {
'constructor': '=?',
'valueOf': '='
},
restrict: 'AE',
template: '<span></span>'
};
});
directive('prototypeMethodNameAsScopeVarB', function() {
return {
scope: {
'constructor': '@?',
'valueOf': '@'
},
restrict: 'AE',
template: '<span></span>'
};
});
directive('prototypeMethodNameAsScopeVarC', function() {
return {
scope: {
'constructor': '&?',
'valueOf': '&'
},
restrict: 'AE',
template: '<span></span>'
};
});
directive('watchAsScopeVar', function() {
return {
scope: {
'watch': '='
},
restrict: 'AE',
template: '<span></span>'
};
});
}));
@@ -2472,6 +2511,118 @@ describe('$compile', function() {
expect(element.isolateScope()).not.toBe($rootScope);
})
);
it('should handle "=" bindings with same method names in Object.prototype correctly when not present', inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-a></div>'
)($rootScope);
};
expect(func).not.toThrow();
expect(element.find('span').scope()).toBe(element.isolateScope());
expect(element.isolateScope()).not.toBe($rootScope);
expect(element.isolateScope()['constructor']).toBe($rootScope.constructor);
expect(element.isolateScope()['valueOf']).toBeUndefined();
})
);
it('should handle "=" bindings with same method names in Object.prototype correctly when present', inject(
function($rootScope, $compile) {
$rootScope.constructor = 'constructor';
$rootScope.valueOf = 'valueOf';
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-a constructor="constructor" value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
expect(element.find('span').scope()).toBe(element.isolateScope());
expect(element.isolateScope()).not.toBe($rootScope);
expect(element.isolateScope()['constructor']).toBe('constructor');
expect(element.isolateScope()['valueOf']).toBe('valueOf');
})
);
it('should handle "@" bindings with same method names in Object.prototype correctly when not present', inject(
function($rootScope, $compile) {
var func = function() {
element = $compile('<div prototype-method-name-as-scope-var-b></div>')($rootScope);
};
expect(func).not.toThrow();
expect(element.find('span').scope()).toBe(element.isolateScope());
expect(element.isolateScope()).not.toBe($rootScope);
expect(element.isolateScope()['constructor']).toBe($rootScope.constructor);
expect(element.isolateScope()['valueOf']).toBeUndefined();
})
);
it('should handle "@" bindings with same method names in Object.prototype correctly when present', inject(
function($rootScope, $compile) {
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-b constructor="constructor" value-of="valueOf"></div>'
)($rootScope);
};
expect(func).not.toThrow();
expect(element.find('span').scope()).toBe(element.isolateScope());
expect(element.isolateScope()).not.toBe($rootScope);
expect(element.isolateScope()['constructor']).toBe('constructor');
expect(element.isolateScope()['valueOf']).toBe('valueOf');
})
);
it('should handle "&" bindings with same method names in Object.prototype correctly when not present', inject(
function($rootScope, $compile) {
var func = function() {
element = $compile('<div prototype-method-name-as-scope-var-c></div>')($rootScope);
};
expect(func).not.toThrow();
expect(element.find('span').scope()).toBe(element.isolateScope());
expect(element.isolateScope()).not.toBe($rootScope);
expect(element.isolateScope()['constructor']).toBe($rootScope.constructor);
expect(element.isolateScope()['valueOf']()).toBeUndefined();
})
);
it('should handle "&" bindings with same method names in Object.prototype correctly when present', inject(
function($rootScope, $compile) {
$rootScope.constructor = function() { return 'constructor'; };
$rootScope.valueOf = function() { return 'valueOf'; };
var func = function() {
element = $compile(
'<div prototype-method-name-as-scope-var-c constructor="constructor()" value-of="valueOf()"></div>'
)($rootScope);
};
expect(func).not.toThrow();
expect(element.find('span').scope()).toBe(element.isolateScope());
expect(element.isolateScope()).not.toBe($rootScope);
expect(element.isolateScope()['constructor']()).toBe('constructor');
expect(element.isolateScope()['valueOf']()).toBe('valueOf');
})
);
it('should not throw exception when using "watch" as binding in Firefox', inject(
function($rootScope, $compile) {
$rootScope.watch = 'watch';
var func = function() {
element = $compile(
'<div watch-as-scope-var watch="watch"></div>'
)($rootScope);
};
expect(func).not.toThrow();
expect(element.find('span').scope()).toBe(element.isolateScope());
expect(element.isolateScope()).not.toBe($rootScope);
expect(element.isolateScope()['watch']).toBe('watch');
})
);
});
@@ -2514,6 +2665,70 @@ describe('$compile', function() {
);
});
});
describe('multidir isolated scope error messages', function() {
angular.module('fakeIsoledScopeModule', [])
.directive('fakeScope', function(log) {
return {
scope: true,
restrict: 'CA',
compile: function() {
return {pre: function(scope, element) {
log(scope.$id);
expect(element.data('$scope')).toBe(scope);
}};
}
};
})
.directive('fakeIScope', function(log) {
return {
scope: {},
restrict: 'CA',
compile: function() {
return function(scope, element) {
iscope = scope;
log(scope.$id);
expect(element.data('$isolateScopeNoTemplate')).toBe(scope);
};
}
};
});
beforeEach(module('fakeIsoledScopeModule', function() {
directive('anonymModuleScopeDirective', function(log) {
return {
scope: true,
restrict: 'CA',
compile: function() {
return {pre: function(scope, element) {
log(scope.$id);
expect(element.data('$scope')).toBe(scope);
}};
}
};
});
}));
it('should add module name to multidir isolated scope message if directive defined through module', inject(
function($rootScope, $compile) {
expect(function() {
$compile('<div class="fake-scope; fake-i-scope"></div>');
}).toThrowMinErr('$compile', 'multidir',
'Multiple directives [fakeIScope (module: fakeIsoledScopeModule), fakeScope (module: fakeIsoledScopeModule)] ' +
'asking for new/isolated scope on: <div class="fake-scope; fake-i-scope">');
})
);
it('sholdn\'t add module name to multidir isolated scope message if directive is defined directly with $compileProvider', inject(
function($rootScope, $compile) {
expect(function() {
$compile('<div class="anonym-module-scope-directive; fake-i-scope"></div>');
}).toThrowMinErr('$compile', 'multidir',
'Multiple directives [anonymModuleScopeDirective, fakeIScope (module: fakeIsoledScopeModule)] ' +
'asking for new/isolated scope on: <div class="anonym-module-scope-directive; fake-i-scope">');
})
);
});
});
});
});
@@ -2777,6 +2992,25 @@ describe('$compile', function() {
}));
it('should handle consecutive text elements as a single text element', inject(function($rootScope, $compile) {
// No point it running the test, if there is no MutationObserver
if (!window.MutationObserver) return;
// Create and register the MutationObserver
var observer = new window.MutationObserver(noop);
observer.observe(document.body, {childList: true, subtree: true});
// Run the actual test
var base = jqLite('<div>&mdash; {{ "This doesn\'t." }}</div>');
element = $compile(base)($rootScope);
$rootScope.$digest();
expect(element.text()).toBe("— This doesn't.");
// Unregister the MutationObserver (and hope it doesn't mess up with subsequent tests)
observer.disconnect();
}));
it('should support custom start/end interpolation symbols in template and directive template',
function() {
module(function($interpolateProvider, $compileProvider) {
@@ -4358,6 +4592,41 @@ describe('$compile', function() {
});
it('should correctly assign controller return values for multiple directives', function() {
var directiveController, otherDirectiveController;
module(function() {
directive('myDirective', function(log) {
return {
scope: true,
controller: function($scope) {
return directiveController = {
foo: 'bar'
};
}
};
});
directive('myOtherDirective', function(log) {
return {
controller: function($scope) {
return otherDirectiveController = {
baz: 'luh'
};
}
};
});
});
inject(function(log, $compile, $rootScope) {
element = $compile('<my-directive my-other-directive></my-directive>')($rootScope);
expect(element.data('$myDirectiveController')).toBe(directiveController);
expect(element.data('$myOtherDirectiveController')).toBe(otherDirectiveController);
});
});
it('should get required parent controller', function() {
module(function() {
directive('nested', function(log) {
@@ -5358,8 +5627,13 @@ describe('$compile', function() {
expect(privateData.events.click).toBeDefined();
expect(privateData.events.click[0]).toBeDefined();
//Ensure the angular $destroy event is still sent
var destroyCount = 0;
element.find("div").on("$destroy", function() { destroyCount++; });
$rootScope.$apply('xs = null');
expect(destroyCount).toBe(2);
expect(firstRepeatedElem.data('$scope')).not.toBeDefined();
privateData = jQuery._data(firstRepeatedElem[0]);
expect(privateData && privateData.events).not.toBeDefined();
@@ -5380,9 +5654,7 @@ describe('$compile', function() {
testCleanup();
// The initial ng-repeat div is dumped after parsing hence we expect cleanData
// count to be one larger than size of the iterated array.
expect(cleanedCount).toBe(xs.length + 1);
expect(cleanedCount).toBe(xs.length);
// Restore the previous jQuery.cleanData.
jQuery.cleanData = currentCleanData;
+65
View File
@@ -1917,6 +1917,71 @@ describe('input', function() {
});
it('should parse exponential notation', function() {
var inputElm = helper.compileInput('<input type="number" name="alias" ng-model="value" />');
// #.###e+##
$rootScope.form.alias.$setViewValue("1.23214124123412412e+26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.23214124123412412e+26);
// #.###e##
$rootScope.form.alias.$setViewValue("1.23214124123412412e26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.23214124123412412e26);
// #.###e-##
$rootScope.form.alias.$setViewValue("1.23214124123412412e-26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.23214124123412412e-26);
// ####e+##
$rootScope.form.alias.$setViewValue("123214124123412412e+26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(123214124123412412e26);
// ####e##
$rootScope.form.alias.$setViewValue("123214124123412412e26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(123214124123412412e26);
// ####e-##
$rootScope.form.alias.$setViewValue("123214124123412412e-26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(123214124123412412e-26);
// #.###E+##
$rootScope.form.alias.$setViewValue("1.23214124123412412E+26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.23214124123412412e+26);
// #.###E##
$rootScope.form.alias.$setViewValue("1.23214124123412412E26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.23214124123412412e26);
// #.###E-##
$rootScope.form.alias.$setViewValue("1.23214124123412412E-26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(1.23214124123412412e-26);
// ####E+##
$rootScope.form.alias.$setViewValue("123214124123412412E+26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(123214124123412412e26);
// ####E##
$rootScope.form.alias.$setViewValue("123214124123412412E26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(123214124123412412e26);
// ####E-##
$rootScope.form.alias.$setViewValue("123214124123412412E-26");
expect(inputElm).toBeValid();
expect($rootScope.value).toBe(123214124123412412e-26);
});
describe('min', function() {
it('should validate', function() {
+25
View File
@@ -1091,6 +1091,31 @@ describe('ngModel', function() {
}));
it('should be possible to extend Object prototype and still be able to do form validation',
inject(function($compile, $rootScope) {
Object.prototype.someThing = function() {};
var element = $compile('<form name="myForm">' +
'<input type="text" name="username" ng-model="username" minlength="10" required />' +
'</form>')($rootScope);
var inputElm = element.find('input');
var formCtrl = $rootScope.myForm;
var usernameCtrl = formCtrl.username;
$rootScope.$digest();
expect(usernameCtrl.$invalid).toBe(true);
expect(formCtrl.$invalid).toBe(true);
usernameCtrl.$setViewValue('valid-username');
$rootScope.$digest();
expect(usernameCtrl.$invalid).toBe(false);
expect(formCtrl.$invalid).toBe(false);
delete Object.prototype.someThing;
dealoc(element);
}));
it('should re-evaluate the form validity state once the asynchronous promise has been delivered',
inject(function($compile, $rootScope, $q) {
+55
View File
@@ -448,6 +448,41 @@ describe('ngOptions', function() {
});
it('should not watch array properties that start with $ or $$', function() {
createSelect({
'ng-options': 'value as createLabel(value) for value in array',
'ng-model': 'selected'
});
scope.createLabel = jasmine.createSpy('createLabel').andCallFake(function(value) { return value; });
scope.array = ['a', 'b', 'c'];
scope.array.$$private = 'do not watch';
scope.array.$property = 'do not watch';
scope.selected = 'b';
scope.$digest();
expect(scope.createLabel).toHaveBeenCalledWith('a');
expect(scope.createLabel).toHaveBeenCalledWith('b');
expect(scope.createLabel).toHaveBeenCalledWith('c');
expect(scope.createLabel).not.toHaveBeenCalledWith('do not watch');
});
it('should not watch object properties that start with $ or $$', function() {
createSelect({
'ng-options': 'key as createLabel(key) for (key, value) in object',
'ng-model': 'selected'
});
scope.createLabel = jasmine.createSpy('createLabel').andCallFake(function(value) { return value; });
scope.object = {'regularProperty': 'visible', '$$private': 'invisible', '$property': 'invisible'};
scope.selected = 'regularProperty';
scope.$digest();
expect(scope.createLabel).toHaveBeenCalledWith('regularProperty');
expect(scope.createLabel).not.toHaveBeenCalledWith('$$private');
expect(scope.createLabel).not.toHaveBeenCalledWith('$property');
});
it('should allow expressions over multiple lines', function() {
scope.isNotFoo = function(item) {
return item.name !== 'Foo';
@@ -1176,6 +1211,26 @@ describe('ngOptions', function() {
});
it('should not set view value again if the tracked property of the model has not changed when using trackBy', function() {
createSelect({
'ng-model': 'selected',
'ng-options': 'item for item in arr track by item.id'
});
scope.$apply(function() {
scope.selected = {id: 10, label: 'ten'};
});
spyOn(element.controller('ngModel'), '$setViewValue');
scope.$apply(function() {
scope.arr[0] = {id: 10, label: 'ten'};
});
expect(element.controller('ngModel').$setViewValue).not.toHaveBeenCalled();
});
it('should not re-render if a property of the model is changed when not using trackBy', function() {
createSelect({
+1031 -735
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -1,12 +1,6 @@
/* global $LogProvider: false */
'use strict';
function initService(debugEnabled) {
return module(function($logProvider) {
$logProvider.debugEnabled(debugEnabled);
});
}
describe('$log', function() {
var $window, logger, log, warn, info, error, debug;
@@ -178,4 +172,11 @@ describe('$log', function() {
expect(errorArgs).toEqual(['abc', 'message\nsourceURL:123']);
});
});
function initService(debugEnabled) {
return module(function($logProvider) {
$logProvider.debugEnabled(debugEnabled);
});
}
});
+10
View File
@@ -1745,6 +1745,16 @@ describe('parser', function() {
expect(scope.$eval("0&&2")).toEqual(0 && 2);
expect(scope.$eval("0||2")).toEqual(0 || 2);
expect(scope.$eval("0||1&&2")).toEqual(0 || 1 && 2);
expect(scope.$eval("true&&a")).toEqual(true && undefined);
expect(scope.$eval("true&&a()")).toEqual(true && undefined);
expect(scope.$eval("true&&a()()")).toEqual(true && undefined);
expect(scope.$eval("true&&a.b")).toEqual(true && undefined);
expect(scope.$eval("true&&a.b.c")).toEqual(true && undefined);
expect(scope.$eval("false||a")).toEqual(false || undefined);
expect(scope.$eval("false||a()")).toEqual(false || undefined);
expect(scope.$eval("false||a()()")).toEqual(false || undefined);
expect(scope.$eval("false||a.b")).toEqual(false || undefined);
expect(scope.$eval("false||a.b.c")).toEqual(false || undefined);
});
it('should parse ternary', function() {
+8
View File
@@ -1643,6 +1643,14 @@ describe('q', function() {
});
describe('resolve', function() {
it('should be an alias of the "when" function', function() {
expect(q.resolve).toBeDefined();
expect(q.resolve).toEqual(q.when);
});
});
describe('optional callbacks', function() {
it('should not require success callback and propagate resolution', function() {
q.when('hi', null, error()).then(success(2), error());
+69
View File
@@ -57,6 +57,31 @@ describe('$aria', function() {
scope.$apply('val = true');
expect(element.attr('aria-hidden')).toBe('userSetValue');
});
it('should always set aria-hidden to a boolean value', function() {
compileElement('<div ng-hide="val"></div>');
scope.$apply('val = "test angular"');
expect(element.attr('aria-hidden')).toBe('true');
scope.$apply('val = null');
expect(element.attr('aria-hidden')).toBe('false');
scope.$apply('val = {}');
expect(element.attr('aria-hidden')).toBe('true');
compileElement('<div ng-show="val"></div>');
scope.$apply('val = "test angular"');
expect(element.attr('aria-hidden')).toBe('false');
scope.$apply('val = null');
expect(element.attr('aria-hidden')).toBe('true');
scope.$apply('val = {}');
expect(element.attr('aria-hidden')).toBe('false');
});
});
@@ -319,6 +344,20 @@ describe('$aria', function() {
scope.$apply('val = true');
expectAriaAttrOnEachElement(element, 'aria-disabled', 'userSetValue');
});
it('should always set aria-disabled to a boolean value', function() {
compileElement('<div ng-disabled="val"></div>');
scope.$apply('val = "test angular"');
expect(element.attr('aria-disabled')).toBe('true');
scope.$apply('val = null');
expect(element.attr('aria-disabled')).toBe('false');
scope.$apply('val = {}');
expect(element.attr('aria-disabled')).toBe('true');
});
});
describe('aria-disabled when disabled', function() {
@@ -510,6 +549,36 @@ describe('$aria', function() {
expectAriaAttrOnEachElement(element, 'aria-valuemin', 'userSetValue2');
expectAriaAttrOnEachElement(element, 'aria-valuemax', 'userSetValue3');
});
it('should update `aria-valuemin/max` when `min/max` changes dynamically', function() {
scope.$apply('min = 25; max = 75');
compileElement('<input type="range" ng-model="val" min="{{min}}" max="{{max}}" />');
expect(element.attr('aria-valuemin')).toBe('25');
expect(element.attr('aria-valuemax')).toBe('75');
scope.$apply('min = 0');
expect(element.attr('aria-valuemin')).toBe('0');
scope.$apply('max = 100');
expect(element.attr('aria-valuemax')).toBe('100');
});
it('should update `aria-valuemin/max` when `ng-min/ng-max` changes dynamically', function() {
scope.$apply('min = 25; max = 75');
compileElement('<input type="range" ng-model="val" ng-min="min" ng-max="max" />');
expect(element.attr('aria-valuemin')).toBe('25');
expect(element.attr('aria-valuemax')).toBe('75');
scope.$apply('min = 0');
expect(element.attr('aria-valuemin')).toBe('0');
scope.$apply('max = 100');
expect(element.attr('aria-valuemax')).toBe('100');
});
});
describe('announcing ngMessages', function() {
+9
View File
@@ -20,6 +20,15 @@ describe('linky', function() {
expect(linky(undefined)).not.toBeDefined();
});
it('should be case-insensitive', function() {
expect(linky('WWW.example.com')).toEqual('<a href="http://WWW.example.com">WWW.example.com</a>');
expect(linky('WWW.EXAMPLE.COM')).toEqual('<a href="http://WWW.EXAMPLE.COM">WWW.EXAMPLE.COM</a>');
expect(linky('HTTP://www.example.com')).toEqual('<a href="HTTP://www.example.com">HTTP://www.example.com</a>');
expect(linky('HTTP://example.com')).toEqual('<a href="HTTP://example.com">HTTP://example.com</a>');
expect(linky('HTTPS://www.example.com')).toEqual('<a href="HTTPS://www.example.com">HTTPS://www.example.com</a>');
expect(linky('HTTPS://example.com')).toEqual('<a href="HTTPS://example.com">HTTPS://example.com</a>');
});
it('should handle www.', function() {
expect(linky('www.example.com')).toEqual('<a href="http://www.example.com">www.example.com</a>');
});