Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe65dd926c | |||
| 669b53ede2 | |||
| b0c3f28e8f | |||
| 9810dc0993 | |||
| ab5df20dfa | |||
| d83a92c121 | |||
| d0425de29e | |||
| ad5e42cf82 | |||
| 9ed1126adb | |||
| 7a19eb84aa | |||
| 718741acab | |||
| ec8bb675b4 | |||
| 8e32f3fd35 | |||
| 02332107e5 | |||
| afc81b554e | |||
| 26e8ab3693 | |||
| 28ccc76aa1 | |||
| b3c4cb7cff | |||
| 4af4378b11 | |||
| 8611ebe6a0 | |||
| 8f46a3c9ac | |||
| 66fdb36ecb | |||
| f0f5ffa9aa | |||
| 2bc7afd3ba | |||
| a4b45397e0 | |||
| de4e06ed73 | |||
| fd822bdaf9 | |||
| 4f78fd692c | |||
| df6d2ba326 | |||
| ccda436f94 | |||
| e86c435349 | |||
| 1942861472 | |||
| b96e978178 | |||
| bda2bba2be | |||
| ca08c004c8 | |||
| 25a62b58db | |||
| 97e3ec4d1b | |||
| 75f11f1fc4 | |||
| e134a8335f | |||
| 8ee32a75f0 | |||
| f6a8ad87ee | |||
| 7e6ff401b8 | |||
| 7aeb6a24f7 | |||
| e1ecc34edd | |||
| 29d36e94e1 | |||
| 091c173632 | |||
| c115fa9924 | |||
| b7a7fc7065 | |||
| 21b2a5bd21 | |||
| ca1e45beaf | |||
| 084b83ffa9 | |||
| bf5e5f7bc9 | |||
| 2e9fed7b6c | |||
| ea3228e311 | |||
| 2eb49147d6 | |||
| 13f92de624 | |||
| 2bc39bb0b4 | |||
| 62ae7fccbc | |||
| 3ace81b92a | |||
| 9acf45127e | |||
| 6883e8c7a0 | |||
| 7ae536d053 | |||
| 2170c06924 | |||
| 0e5a24c584 | |||
| 4e8f0d6e9f | |||
| f9b6b61468 | |||
| 555f415290 | |||
| 3800d17703 | |||
| 009059dd1b | |||
| 6b7ddf414d | |||
| 8259f10138 | |||
| ab407de54d | |||
| 0d7fe97aff | |||
| a2a830e227 | |||
| 8336f3f0ba | |||
| e14ac2c3b0 | |||
| a13653c814 | |||
| 8017340cd1 | |||
| 17d1aef66a | |||
| 1856f62cb1 | |||
| 7e1f364177 | |||
| aac68bf2ba | |||
| 4b4292edb8 | |||
| 292d5d1421 | |||
| 66dec77555 | |||
| 8fa79066e2 | |||
| 909415d5ed | |||
| 4421f3d435 | |||
| 22cb600280 | |||
| 5ba227c7cd | |||
| f37f0ea16e | |||
| 91ccb4ba6e | |||
| d0f459c56f | |||
| cbedf55641 | |||
| 988ed451b5 | |||
| fc2f188d4d | |||
| 4b1913c5ec | |||
| 06534413d3 | |||
| e54909f5ef | |||
| 79f2512ba7 | |||
| b0eb831bce | |||
| 7f0b97e02c | |||
| fc7f1ef6a0 |
@@ -10,3 +10,6 @@ performance/temp*.html
|
||||
*~
|
||||
angular.js.tmproj
|
||||
node_modules
|
||||
jsTestDriver*.conf
|
||||
angular.xcodeproj
|
||||
.idea
|
||||
|
||||
+178
-30
@@ -1,12 +1,158 @@
|
||||
- The Latest Stable Release: <a href="#0.9.19">0.9.19 canine-psychokinesis</a>
|
||||
- The Latest Unstable Release: <a href="#0.10.0">0.10.0 chicken-hands</a>
|
||||
- The Latest Unstable Release: <a href="#0.10.3">0.10.3 shattering-heartbeat</a>
|
||||
|
||||
|
||||
<a name="0.10.0"><a/>
|
||||
# 0.10.0 chicken-hands (2011-09-02) #
|
||||
<a name="0.10.3"></a>
|
||||
# 0.10.3 shattering-heartbeat (2011-10-13) #
|
||||
|
||||
## Features:
|
||||
|
||||
- New forms, validation, support for HTML5 input widgets. Please check out:
|
||||
- [Forms overview](http://docs-next.angularjs.org/guide/dev_guide.forms)
|
||||
- [form widget](http://docs-next.angularjs.org/api/angular.widget.form)
|
||||
- [input widget](http://docs-next.angularjs.org/api/angular.widget.input)
|
||||
- [$formFactory service](http://docs-next.angularjs.org/api/angular.service.$formFactory)
|
||||
- [angular.inputType](http://docs-next.angularjs.org/api/angular.inputType)
|
||||
- [commit](https://github.com/angular/angular.js/commit/4f78fd692c0ec51241476e6be9a4df06cd62fdd6)
|
||||
|
||||
- [ng:repeat] now has element-model affinity, which makes it more friendly to third-party code that
|
||||
is not aware of angular's DOM manipulation. This is also the pre-requisite for supporting
|
||||
animations.
|
||||
([commit](https://github.com/angular/angular.js/commit/75f11f1fc46c35a28c0905f7316ea6779145e2fb))
|
||||
|
||||
|
||||
## Bug Fixes:
|
||||
|
||||
- The select widget with [ng:options] directive now correctly displays selected option (regression
|
||||
from 0.10.2).
|
||||
- Fix for jqLite's removeClass, which under certain circumstances could clobber class names.
|
||||
([commit](https://github.com/angular/angular.js/commit/b96e978178a6acbf048aa6db466ed845e1395445))
|
||||
- Other small fixes and documentation improvements.
|
||||
|
||||
|
||||
## Breaking Changes:
|
||||
|
||||
- Due to changes in how forms and validation works the following were replaced with new apis:
|
||||
- `angular.formatter` - use `angular.inputType` or form's `$createWidget`
|
||||
- `angular.validator` - use `angular.inputType` or form's `$createWidget`
|
||||
- changes to `<input>` and `<select>` elements
|
||||
- `ng:model` directive is now required for data-binding to kick in
|
||||
- the `name` attribute is now optional and is used only as an alias when accessing the input
|
||||
widget via the form object.
|
||||
- view can't affect the model without a user interaction, so the `value` attribute of the
|
||||
`<input>` element and `selected` attribute of the `<option>` element if specified in the
|
||||
template is ignored.
|
||||
- Removed decoration of DOM elements when:
|
||||
- an exception occurs - when an exception happens, it will be passed to the $exceptionHandler
|
||||
service, which can decide what to do with it.
|
||||
- an input widget contains invalid input - in this case the forms validation apis can be used to
|
||||
display a customized error message.
|
||||
- The $hover service was removed (it was needed only for the DOM decoration described above).
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.10.2"></a>
|
||||
# 0.10.2 sneaky-seagull (2011-10-08) #
|
||||
|
||||
## Features:
|
||||
|
||||
- jQuery 1.6.4 support (Issue [#556](https://github.com/angular/angular.js/issues/556))
|
||||
- [jqLite](http://docs-next.angularjs.org/api/angular.element) improvements:
|
||||
- Added support for `prop` method
|
||||
([commit](https://github.com/angular/angular.js/commit/3800d177030d20c5c3d04e3601f892c46e723dc2))
|
||||
- Added support for `unbind` method
|
||||
([commit](https://github.com/angular/angular.js/commit/6b7ddf414de82720bbf547b2fa661bf5fcec7bb6))
|
||||
|
||||
|
||||
## Bug Fixes:
|
||||
|
||||
- Added support for short-circuiting of && and || operators in in angular expressions
|
||||
(Issue [#433](https://github.com/angular/angular.js/issues/433))
|
||||
- Fix for [$limitTo] to properly handle excessive limits (contributed by tehek)
|
||||
(Issue [#571](https://github.com/angular/angular.js/issues/571))
|
||||
- [jqLite]'s css() method now converts dash-separated css property names to camelCase in order to
|
||||
support dash-separated properties on Firefox
|
||||
(Issue [#569](https://github.com/angular/angular.js/issues/569))
|
||||
- action defaults for [$resource]s now take precedence over resource defaults (contributed by
|
||||
Marcello Nuccio)
|
||||
([commit](https://github.com/angular/angular.js/commit/bf5e5f7bc9ebc7dc6cf8fdf3c4923498b22a8654))
|
||||
- Fixed escaping issues in [$route] matcher
|
||||
([commit](https://github.com/angular/angular.js/commit/2bc39bb0b4f81b77597bb52f8572d231cf4f83e2))
|
||||
- Fixed two issues in $browser.defer.cancel mock
|
||||
([commit](https://github.com/angular/angular.js/commit/62ae7fccbc524ff498779564294ed6e1a7a3f51c),
|
||||
[commit](https://github.com/angular/angular.js/commit/8336f3f0ba89b529057027711ab4babd6c2cb649))
|
||||
- Fix for ng:options, which under certain circumstances didn't select the right option element
|
||||
([commit](https://github.com/angular/angular.js/commit/555f4152909e1c0bd5400737a62dc5d63ecd32d3))
|
||||
|
||||
|
||||
## Docs:
|
||||
|
||||
- migrated the docs app to use [$location]'s HTML5 mode (hashbang urls no more)
|
||||
([commit](https://github.com/angular/angular.js/commit/13f92de6246a0af8450fde84b209211a56397fda))
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- If Angular is being used with jQuery older than 1.6, some features might not work properly. Please
|
||||
upgrade to jQuery version 1.6.4.
|
||||
|
||||
## Breaking Changes
|
||||
- ng:repeat no longer has ng:repeat-index property. This is because the elements now have
|
||||
affinity to the underlying collection, and moving items around in the collection would move
|
||||
ng:repeat-index property rendering it meaningless.
|
||||
|
||||
|
||||
<a name="0.10.1"></a>
|
||||
# 0.10.1 inexorable-juggernaut (2011-09-09) #
|
||||
|
||||
## Features
|
||||
|
||||
- complete rewrite of the $location service with HTML5 support, many API and semantic changes.
|
||||
Please see:
|
||||
- [$location service API docs](http://docs-next.angularjs.org/#!/api/angular.service.$location)
|
||||
- [$location service dev guide article](http://docs-next.angularjs.org/#!/guide/dev_guide.services.$location)
|
||||
- [location.js source file](https://github.com/angular/angular.js/blob/master/src/service/location.js)
|
||||
- breaking changes section of this changelog
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- $xhr should not covert HTTP status 0 to 200
|
||||
([commit](https://github.com/angular/angular.js/commit/b0eb831bce7d0ea066fd0758124793ed3db6d692))
|
||||
- fixed several doc examples that were broken on IE
|
||||
- ng:change should be called after the new val is set
|
||||
(Issue [#547](https://github.com/angular/angular.js/issues/547))
|
||||
- currency filter should return an empty string for non-numbers
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
- $location related changes - for complete list of api changes see:
|
||||
[Migrating from earlier AngularJS releases](http://docs-next.angularjs.org/#!/guide/dev_guide.services.$location)
|
||||
- $location api changes:
|
||||
- $location.href -> $location.absUrl()
|
||||
- $location.hash -> $location.url()
|
||||
- $location.hashPath -> $location.path()
|
||||
- $location.hashSearch -> $location.search()
|
||||
- $location.search -> no equivalent, use $window.location.search (this is so that we can work in
|
||||
hashBang and html5 mode at the same time, check out the docs)
|
||||
- $location.update() / $location.updateHash() -> use $location.url()
|
||||
- n/a -> $location.replace() - new api for replacing history record instead of creating a new one
|
||||
|
||||
- $location semantic changes:
|
||||
- all url pieces are always in sync ($location.path(), $location.url(), $location.search(), ...) -
|
||||
this was previously true only if you used update* methods instead of direct assignment
|
||||
($location.hashPath = 'foo')
|
||||
- we now use (window.history.pushState || onHashChange event || polling) for detecting url changes
|
||||
in the browser (we use the best one available).
|
||||
|
||||
|
||||
|
||||
<a name="0.10.0"></a>
|
||||
# 0.10.0 chicken-hands (2011-09-02) #
|
||||
|
||||
## Features
|
||||
|
||||
- complete rewrite of the Scope implementation with several API and semantic changes. Please see:
|
||||
- [angular.scope API docs](http://docs-next.angularjs.org/#!/api/angular.scope)
|
||||
- [scopes dev guide article](http://docs-next.angularjs.org/#!/guide/dev_guide.scopes)
|
||||
@@ -18,7 +164,7 @@
|
||||
- added [ng:cloak] directive for hiding uncompiled templates
|
||||
|
||||
|
||||
## Bug Fix:
|
||||
## Bug Fixes
|
||||
|
||||
- make [ng:class] friendly towards other code adding/removing classes
|
||||
([commit](https://github.com/angular/angular.js/commit/2a8fe56997fddbad673748ce02abf649a709c4ca))
|
||||
@@ -29,7 +175,7 @@
|
||||
- expose missing [lowercase], [uppercase] and [isDate] APIs.
|
||||
|
||||
|
||||
## Docs:
|
||||
## Docs
|
||||
|
||||
- many (but not all just yet) api docs were proof-read and improved
|
||||
|
||||
@@ -57,7 +203,7 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.19"><a/>
|
||||
<a name="0.9.19"></a>
|
||||
# 0.9.19 canine-psychokinesis (2011-08-20) #
|
||||
|
||||
## Features
|
||||
@@ -71,7 +217,7 @@
|
||||
([commit](https://github.com/angular/angular.js/commit/e004378d100ce767a1107180102790a9a360644e))
|
||||
|
||||
|
||||
## Fixes
|
||||
## Bug Fixes
|
||||
- fixed memory leak found in [ng:options] directive
|
||||
([commit](https://github.com/angular/angular.js/commit/6aa04b1db48853340d720e0a1a3e325ac523a06f))
|
||||
- make ng:class-even/odd compatible with ng:class
|
||||
@@ -93,7 +239,7 @@
|
||||
`css('display', 'block'/'inline'/..)` instead
|
||||
|
||||
|
||||
<a name="0.9.18"><a/>
|
||||
<a name="0.9.18"></a>
|
||||
# 0.9.18 jiggling-armfat (2011-07-29) #
|
||||
|
||||
### Features
|
||||
@@ -126,7 +272,7 @@
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- make injector compatible with Rhino (HtmlUnit) (contributed by Mårten Dolk)
|
||||
- make injector compatible with Rhino (HtmlUnit) (contributed by Mårten Dolk)
|
||||
[commit](https://github.com/angular/angular.js/commit/77ba539f630c57b17d71dbf1e9c5667a7eb603b7)
|
||||
- `ie-compat.js` fixes and improvements related to fetching this file on the fly on legacy browsers
|
||||
- [jqLite]
|
||||
@@ -163,7 +309,7 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.17"><a/>
|
||||
<a name="0.9.17"></a>
|
||||
# <angular/> 0.9.17 vegetable-reanimation (2011-06-30) #
|
||||
|
||||
### New Features
|
||||
@@ -198,7 +344,7 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.16"><a/>
|
||||
<a name="0.9.16"></a>
|
||||
# <angular/> 0.9.16 weather-control (2011-06-07) #
|
||||
|
||||
### Features
|
||||
@@ -229,7 +375,7 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.15"><a/>
|
||||
<a name="0.9.15"></a>
|
||||
# <angular/> 0.9.15 lethal-stutter (2011-04-11) #
|
||||
|
||||
### Features
|
||||
@@ -248,7 +394,7 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.14"><a/>
|
||||
<a name="0.9.14"></a>
|
||||
# <angular/> 0.9.14 key-maker (2011-04-01) #
|
||||
|
||||
### Performance
|
||||
@@ -269,12 +415,12 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.13"><a/>
|
||||
<a name="0.9.13"></a>
|
||||
# <angular/> 0.9.13 curdling-stare (2011-03-13) #
|
||||
|
||||
### New Features
|
||||
- Added XSRF protection for the [$xhr] service. (commit c578f8c3)
|
||||
- Targeted auto-bootstrap — [ng:autobind] now takes an optional value which specifies an element id
|
||||
- Targeted auto-bootstrap — [ng:autobind] now takes an optional value which specifies an element id
|
||||
to be compiled instead of compiling the entire html document. (commit 9d5c5337)
|
||||
|
||||
|
||||
@@ -296,7 +442,7 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.12"><a/>
|
||||
<a name="0.9.12"></a>
|
||||
# <angular/> 0.9.12 thought-implanter (2011-03-03) #
|
||||
|
||||
### API
|
||||
@@ -353,7 +499,7 @@
|
||||
|
||||
|
||||
|
||||
<a name="0.9.11"><a/>
|
||||
<a name="0.9.11"></a>
|
||||
# <angular/> 0.9.11 snow-maker (2011-02-08) #
|
||||
|
||||
### Documentation
|
||||
@@ -384,7 +530,7 @@
|
||||
request via the `$xhr` service or remove all unneeded `flush()` calls.
|
||||
|
||||
|
||||
<a name="0.9.10"><a/>
|
||||
<a name="0.9.10"></a>
|
||||
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
|
||||
|
||||
### Features
|
||||
@@ -408,7 +554,7 @@ with the `$route` service
|
||||
- lots of improvements related to formatting of the content of docs.anguarjs.org
|
||||
|
||||
|
||||
<a name="0.9.9"><a/>
|
||||
<a name="0.9.9"></a>
|
||||
# <angular/> 0.9.9 time-shift (2011-01-13) #
|
||||
|
||||
### Security
|
||||
@@ -439,7 +585,7 @@ with the `$route` service
|
||||
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
|
||||
|
||||
### Breaking changes
|
||||
- API for accessing registered services — `scope.$inject` — was renamed to
|
||||
- API for accessing registered services — `scope.$inject` — was renamed to
|
||||
[`scope.$service`](http://docs.angularjs.org/#!/api/angular.scope.$service). (commit b2631f61)
|
||||
|
||||
- Support for `eager-published` services was removed. This change was done to make explicit
|
||||
@@ -484,7 +630,7 @@ with the `$route` service
|
||||
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
|
||||
|
||||
|
||||
<a name="0.9.8"><a/>
|
||||
<a name="0.9.8"></a>
|
||||
# <angular/> 0.9.8 astral-projection (2010-12-23) #
|
||||
|
||||
### Docs/Getting started
|
||||
@@ -498,7 +644,7 @@ with the `$route` service
|
||||
- Ignore input widgets which have no name (issue #153)
|
||||
|
||||
|
||||
<a name="0.9.7"><a/>
|
||||
<a name="0.9.7"></a>
|
||||
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
|
||||
|
||||
### Bug Fixes
|
||||
@@ -517,7 +663,7 @@ with the `$route` service
|
||||
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
|
||||
|
||||
|
||||
<a name="0.9.6"><a/>
|
||||
<a name="0.9.6"></a>
|
||||
# <angular/> 0.9.6 night-vision (2010-12-06) #
|
||||
|
||||
### Security
|
||||
@@ -547,7 +693,7 @@ with the `$route` service
|
||||
- The HTML sanitizer is slightly more strinct now. Please see info in the "Security" section above.
|
||||
|
||||
|
||||
<a name="0.9.5"><a/>
|
||||
<a name="0.9.5"></a>
|
||||
# <angular/> 0.9.5 turkey-blast (2010-11-25) #
|
||||
|
||||
### Docs
|
||||
@@ -557,7 +703,7 @@ with the `$route` service
|
||||
- added `angular.Array.limitTo` to make it easy to select first or last few items of an array
|
||||
|
||||
|
||||
<a name="0.9.4"><a/>
|
||||
<a name="0.9.4"></a>
|
||||
# <angular/> 0.9.4 total-recall (2010-11-18) #
|
||||
|
||||
### Docs
|
||||
@@ -574,7 +720,7 @@ with the `$route` service
|
||||
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
|
||||
|
||||
|
||||
<a name="0.9.3"><a/>
|
||||
<a name="0.9.3"></a>
|
||||
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
|
||||
|
||||
### Docs
|
||||
@@ -602,7 +748,7 @@ with the `$route` service
|
||||
simple RegExp validator.
|
||||
|
||||
|
||||
<a name="0.9.2"><a/>
|
||||
<a name="0.9.2"></a>
|
||||
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
|
||||
|
||||
### Docs
|
||||
@@ -640,7 +786,7 @@ with the `$route` service
|
||||
implements HEAD
|
||||
|
||||
|
||||
<a name="0.9.1"><a/>
|
||||
<a name="0.9.1"></a>
|
||||
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
|
||||
|
||||
### Security
|
||||
@@ -667,7 +813,7 @@ with the `$route` service
|
||||
- html filter now sanitizes html content for XSS attacks which may result in different behavior
|
||||
|
||||
|
||||
<a name="0.9.0"><a/>
|
||||
<a name="0.9.0"></a>
|
||||
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
|
||||
|
||||
### Security
|
||||
@@ -746,3 +892,5 @@ with the `$route` service
|
||||
[$on]: http://docs-next.angularjs.org/#!/api/angular.scope.$on
|
||||
[$emit]: http://docs-next.angularjs.org/#!/api/angular.scope.$emit
|
||||
[$broadcast]: http://docs-next.angularjs.org/#!/api/angular.scope.$broadcast
|
||||
[$limitTo]: http://docs-next.angularjs.org/api/angular.Array.limitTo
|
||||
[$location]: http://docs-next.angularjs.org/api/angular.service.$location
|
||||
|
||||
@@ -1,61 +1,8 @@
|
||||
require 'yaml'
|
||||
include FileUtils
|
||||
|
||||
ANGULAR = [
|
||||
'src/Angular.js',
|
||||
'src/JSON.js',
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/formatters.js',
|
||||
'src/validators.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/hover.js',
|
||||
'src/service/invalidWidgets.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/route.js',
|
||||
'src/service/routeParams.js',
|
||||
'src/service/window.js',
|
||||
'src/service/xhr.bulk.js',
|
||||
'src/service/xhr.cache.js',
|
||||
'src/service/xhr.error.js',
|
||||
'src/service/xhr.js',
|
||||
'src/service/locale.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
'src/AngularPublic.js',
|
||||
]
|
||||
|
||||
ANGULAR_SCENARIO = [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js'
|
||||
]
|
||||
content = File.open('angularFiles.js', 'r') {|f| f.read }
|
||||
files = eval(content.gsub(/angularFiles = /, '').gsub(/:/, '=>'));
|
||||
|
||||
BUILD_DIR = 'build'
|
||||
|
||||
@@ -89,10 +36,10 @@ desc 'Compile Scenario'
|
||||
task :compile_scenario => :init do
|
||||
|
||||
deps = [
|
||||
'lib/jquery/jquery-1.4.2.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'src/scenario/angular.prefix',
|
||||
ANGULAR,
|
||||
ANGULAR_SCENARIO,
|
||||
files['angularSrc'],
|
||||
files['angularScenario'],
|
||||
'src/scenario/angular.suffix',
|
||||
]
|
||||
|
||||
@@ -130,70 +77,12 @@ task :compile_jstd_scenario_adapter => :init do
|
||||
end
|
||||
|
||||
|
||||
desc 'Generate IE css js patch'
|
||||
task :generate_ie_compat => :init do
|
||||
css = File.open('css/angular.css', 'r') {|f| f.read }
|
||||
|
||||
# finds all css rules that contain backround images and extracts the rule name(s), content type of
|
||||
# the image and base64 encoded image data
|
||||
r = /\n([^\{\n]+)\s*\{[^\}]*background-image:\s*url\("data:([^;]+);base64,([^"]+)"\);[^\}]*\}/
|
||||
|
||||
images = css.scan(r)
|
||||
|
||||
# create a js file with multipart header containing the extracted images. the entire file *must*
|
||||
# be CRLF (\r\n) delimited
|
||||
File.open(path_to('angular-ie-compat.js'), 'w') do |f|
|
||||
f.write("/*\r\n" +
|
||||
"Content-Type: multipart/related; boundary=\"_\"\r\n" +
|
||||
"\r\n")
|
||||
|
||||
images.each_index do |idx|
|
||||
f.write("--_\r\n" +
|
||||
"Content-Location:img#{idx}\r\n" +
|
||||
"Content-Transfer-Encoding:base64\r\n" +
|
||||
"\r\n" +
|
||||
images[idx][2] + "\r\n")
|
||||
end
|
||||
|
||||
f.write("--_--\r\n" +
|
||||
"*/\r\n")
|
||||
|
||||
# generate a css string containing *background-image rules for IE that point to the mime type
|
||||
# images in the header
|
||||
cssString = ''
|
||||
images.each_index do |idx|
|
||||
cssString += "#{images[idx][0]}{*background-image:url(\"mhtml:' + jsUri + '!img#{idx}\")}"
|
||||
end
|
||||
|
||||
# generate a javascript closure that contains a function which will append the generated css
|
||||
# string as a stylesheet to the current html document
|
||||
jsString = "(function(){ \r\n" +
|
||||
" var jsUri = document.location.href.replace(/\\/[^\\\/]+(#.*)?$/, '/') + \r\n" +
|
||||
" document.getElementById('ng-ie-compat').src,\r\n" +
|
||||
" css = '#{cssString}',\r\n" +
|
||||
" s = document.createElement('style'); \r\n" +
|
||||
"\r\n" +
|
||||
" s.setAttribute('type', 'text/css'); \r\n" +
|
||||
"\r\n" +
|
||||
" if (s.styleSheet) { \r\n" +
|
||||
" s.styleSheet.cssText = css; \r\n" +
|
||||
" } else { \r\n" +
|
||||
" s.appendChild(document.createTextNode(css)); \r\n" +
|
||||
" } \r\n" +
|
||||
" document.getElementsByTagName('head')[0].appendChild(s); \r\n" +
|
||||
"})();\r\n"
|
||||
|
||||
f.write(jsString)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc 'Compile JavaScript'
|
||||
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter, :generate_ie_compat] do
|
||||
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter] do
|
||||
|
||||
deps = [
|
||||
'src/angular.prefix',
|
||||
ANGULAR,
|
||||
files['angularSrc'],
|
||||
'src/angular.suffix',
|
||||
]
|
||||
|
||||
@@ -246,7 +135,6 @@ task :package => [:clean, :compile, :docs] do
|
||||
['src/angular-mocks.js',
|
||||
path_to('angular.js'),
|
||||
path_to('angular.min.js'),
|
||||
path_to('angular-ie-compat.js'),
|
||||
path_to('angular-scenario.js'),
|
||||
path_to('jstd-scenario-adapter.js'),
|
||||
path_to('jstd-scenario-adapter-config.js'),
|
||||
@@ -262,7 +150,8 @@ task :package => [:clean, :compile, :docs] do
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
@@ -270,10 +159,46 @@ task :package => [:clean, :compile, :docs] do
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-nocache.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-nocache.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index-jq-debug.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('../angular.js', "../angular-#{NG_VERSION.full}.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/docs-scenario.html", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
@@ -285,14 +210,16 @@ task :package => [:clean, :compile, :docs] do
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/appcache-offline.manifest", File::RDWR) do |f|
|
||||
text = f.read
|
||||
f.truncate 0
|
||||
f.rewind
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js")
|
||||
f.write text.sub('angular.min.js', "angular-#{NG_VERSION.full}.min.js").
|
||||
sub('/build/docs/', "/#{NG_VERSION.full}/docs-#{NG_VERSION.full}/")
|
||||
end
|
||||
|
||||
|
||||
|
||||
Vendored
+141
@@ -0,0 +1,141 @@
|
||||
angularFiles = {
|
||||
'angularSrc': [
|
||||
'src/Angular.js',
|
||||
'src/JSON.js',
|
||||
'src/Compiler.js',
|
||||
'src/Scope.js',
|
||||
'src/Injector.js',
|
||||
'src/parser.js',
|
||||
'src/Resource.js',
|
||||
'src/Browser.js',
|
||||
'src/sanitizer.js',
|
||||
'src/jqLite.js',
|
||||
'src/apis.js',
|
||||
'src/filters.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/formFactory.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/route.js',
|
||||
'src/service/routeParams.js',
|
||||
'src/service/sniffer.js',
|
||||
'src/service/window.js',
|
||||
'src/service/xhr.bulk.js',
|
||||
'src/service/xhr.cache.js',
|
||||
'src/service/xhr.error.js',
|
||||
'src/service/xhr.js',
|
||||
'src/service/locale.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
'src/widget/form.js',
|
||||
'src/widget/input.js',
|
||||
'src/widget/select.js',
|
||||
'src/AngularPublic.js',
|
||||
],
|
||||
|
||||
'angularScenario': [
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/Application.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Future.js',
|
||||
'src/scenario/ObjectModel.js',
|
||||
'src/scenario/Describe.js',
|
||||
'src/scenario/Runner.js',
|
||||
'src/scenario/SpecRunner.js',
|
||||
'src/scenario/dsl.js',
|
||||
'src/scenario/matchers.js',
|
||||
'src/scenario/output/Html.js',
|
||||
'src/scenario/output/Json.js',
|
||||
'src/scenario/output/Xml.js',
|
||||
'src/scenario/output/Object.js'
|
||||
],
|
||||
|
||||
'jstd': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_remove.js',
|
||||
'@angularSrc',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'test/widget/*.js',
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdExclude': [
|
||||
'test/jquery_alias.js',
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'src/AngularPublic.js'
|
||||
],
|
||||
|
||||
'jstdScenario': [
|
||||
'build/angular-scenario.js',
|
||||
'build/jstd-scenario-adapter-config.js',
|
||||
'build/jstd-scenario-adapter.js',
|
||||
'build/docs/docs-scenario.js'
|
||||
],
|
||||
|
||||
'jstdPerf': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'angularSrc',
|
||||
'src/angular-mocks.js',
|
||||
'perf/data/*.js',
|
||||
'perf/testUtils.js',
|
||||
'perf/*.js'
|
||||
],
|
||||
|
||||
'jstdPerfExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'src/AngularPublic.js'
|
||||
],
|
||||
|
||||
'jstdJquery': [
|
||||
'lib/jasmine/jasmine.js',
|
||||
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
|
||||
'lib/jquery/jquery.js',
|
||||
'test/jquery_alias.js',
|
||||
'@angularSrc',
|
||||
'example/personalLog/*.js',
|
||||
'test/testabilityPatch.js',
|
||||
'src/scenario/Scenario.js',
|
||||
'src/scenario/output/*.js',
|
||||
'src/jstd-scenario-adapter/*.js',
|
||||
'src/scenario/*.js',
|
||||
'src/angular-mocks.js',
|
||||
'test/mocks.js',
|
||||
'test/scenario/*.js',
|
||||
'test/scenario/output/*.js',
|
||||
'test/jstd-scenario-adapter/*.js',
|
||||
'test/*.js',
|
||||
'test/service/*.js',
|
||||
'test/widget/*.js',
|
||||
'example/personalLog/test/*.js'
|
||||
],
|
||||
|
||||
'jstdJqueryExclude': [
|
||||
'src/angular-bootstrap.js',
|
||||
'src/AngularPublic.js',
|
||||
'src/scenario/angular-bootstrap.js',
|
||||
'test/jquery_remove.js'
|
||||
]
|
||||
}
|
||||
@@ -7,85 +7,3 @@
|
||||
.ng-format-negative {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.ng-exception {
|
||||
border: 2px solid #FF0000;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
font-size: smaller;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.ng-validation-error {
|
||||
border: 2px solid #FF0000;
|
||||
}
|
||||
|
||||
|
||||
/*****************
|
||||
* TIP
|
||||
*****************/
|
||||
#ng-callout {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
font-family: Verdana, Arial, Helvetica, sans-serif;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#ng-callout .ng-arrow-left{
|
||||
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrSLoc/AG8FeUUIN+sGebWAnbKSJodqqlsOxJtqYooU9vvk+vcJIcTkg+QAAA7");
|
||||
background-repeat: no-repeat;
|
||||
background-position: left top;
|
||||
position: absolute;
|
||||
z-index:101;
|
||||
left:-12px;
|
||||
height:23px;
|
||||
width:10px;
|
||||
top:-3px;
|
||||
}
|
||||
|
||||
#ng-callout .ng-arrow-right{
|
||||
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrCLTcoM29yN6k9socs91e5X3EyJloipYrO4ohTMqA0Fn2XVNswJe+H+SXAAA7");
|
||||
background-repeat: no-repeat;
|
||||
background-position: left top;
|
||||
position: absolute;
|
||||
z-index:101;
|
||||
height:23px;
|
||||
width:11px;
|
||||
top:-2px;
|
||||
}
|
||||
|
||||
#ng-callout {
|
||||
position: absolute;
|
||||
z-index:100;
|
||||
border: 2px solid #CCCCCC;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#ng-callout .ng-content{
|
||||
padding:10px 10px 10px 10px;
|
||||
color:#333333;
|
||||
}
|
||||
|
||||
#ng-callout .ng-title{
|
||||
background-color: #CCCCCC;
|
||||
text-align: left;
|
||||
padding-left: 8px;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 2px;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
|
||||
/*****************
|
||||
* indicators
|
||||
*****************/
|
||||
.ng-input-indicator-wait {
|
||||
background-image: url("data:image/png;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==");
|
||||
background-position: right;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
@ngdoc overview
|
||||
@name angular.inputType
|
||||
@description
|
||||
|
||||
Angular {@link guide/dev_guide.forms forms} allow you to build complex widgets. However for
|
||||
simple widget which are based on HTML input text element a simpler way of providing the validation
|
||||
and parsing is also provided. `angular.inputType` is a short hand for creating a widget which
|
||||
already has the DOM listeners and `$render` method supplied. The only thing which needs to
|
||||
be provided by the developer are the optional `$validate` listener and
|
||||
`$parseModel` or `$parseModel` methods.
|
||||
|
||||
All `inputType` widgets support:
|
||||
|
||||
- CSS classes:
|
||||
- **`ng-valid`**: when widget is valid.
|
||||
- **`ng-invalid`**: when widget is invalid.
|
||||
- **`ng-pristine`**: when widget has not been modified by user action.
|
||||
- **`ng-dirty`**: when has been modified do to user action.
|
||||
|
||||
- Widget properties:
|
||||
- **`$valid`**: When widget is valid.
|
||||
- **`$invalid`**: When widget is invalid.
|
||||
- **`$pristine`**: When widget has not been modified by user interaction.
|
||||
- **`$dirty`**: When user has been modified do to user interaction.
|
||||
- **`$required`**: When the `<input>` element has `required` attribute. This means that the
|
||||
widget will have `REQUIRED` validation error if empty.
|
||||
- **`$disabled`**: When the `<input>` element has `disabled` attribute.
|
||||
- **`$readonly`**: When the `<input>` element has `readonly` attribute.
|
||||
|
||||
- Widget Attribute Validators:
|
||||
- **`required`**: Sets `REQUIRED` validation error key if the input is empty
|
||||
- **`ng:pattern`** Sets `PATTERN` validation error key if the value does not match the
|
||||
RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
|
||||
patterns defined as scope expressions.
|
||||
|
||||
|
||||
|
||||
# Example
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.inputType('json', function() {
|
||||
this.$parseView = function() {
|
||||
try {
|
||||
this.$modelValue = angular.fromJson(this.$viewValue);
|
||||
if (this.$error.JSON) {
|
||||
this.$emit('$valid', 'JSON');
|
||||
}
|
||||
} catch (e) {
|
||||
this.$emit('$invalid', 'JSON');
|
||||
}
|
||||
}
|
||||
|
||||
this.$parseModel = function() {
|
||||
this.$viewValue = angular.toJson(this.$modelValue);
|
||||
}
|
||||
});
|
||||
|
||||
function Ctrl() {
|
||||
this.data = {
|
||||
framework:'angular',
|
||||
codenames:'supper-powers'
|
||||
}
|
||||
this.required = false;
|
||||
this.disabled = false;
|
||||
this.readonly = false;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="Ctrl">
|
||||
<form name="myForm">
|
||||
<input type="json" ng:model="data" size="80"
|
||||
ng:required="{{required}}" ng:disabled="{{disabled}}"
|
||||
ng:readonly="{{readonly}}"/><br/>
|
||||
Required: <input type="checkbox" ng:model="required"> <br/>
|
||||
Disabled: <input type="checkbox" ng:model="disabled"> <br/>
|
||||
Readonly: <input type="checkbox" ng:model="readonly"> <br/>
|
||||
<pre>data={{data}}</pre>
|
||||
<pre>myForm={{myForm}}</pre>
|
||||
</form>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should invalidate on wrong input', function() {
|
||||
expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');
|
||||
input('data').enter('{}');
|
||||
expect(binding('data')).toEqual('data={\n }');
|
||||
input('data').enter('{');
|
||||
expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.service
|
||||
@description
|
||||
@@ -14,8 +13,6 @@ session cookies
|
||||
* {@link angular.service.$document $document } - Provides reference to `window.document` element
|
||||
* {@link angular.service.$exceptionHandler $exceptionHandler } - Receives uncaught angular
|
||||
exceptions
|
||||
* {@link angular.service.$hover $hover } -
|
||||
* {@link angular.service.$invalidWidgets $invalidWidgets } - Holds references to invalid widgets
|
||||
* {@link angular.service.$location $location } - Parses the browser location URL
|
||||
* {@link angular.service.$log $log } - Provides logging service
|
||||
* {@link angular.service.$resource $resource } - Creates objects for interacting with RESTful
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
* {@link angular.directive Directives} - Angular DOM element attributes
|
||||
* {@link angular.markup Markup} and {@link angular.attrMarkup Attribute Markup}
|
||||
* {@link angular.filter Filters} - Angular output filters
|
||||
* {@link angular.formatter Formatters} - Angular converters for form elements
|
||||
* {@link angular.validator Validators} - Angular input validators
|
||||
* {@link angular.compile angular.compile()} - Template compiler
|
||||
|
||||
## Angular Scope API
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Advanced Form
|
||||
@description
|
||||
@@ -9,9 +8,7 @@ detection, and preventing invalid form submission.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
UserForm.$inject = ['$invalidWidgets'];
|
||||
function UserForm($invalidWidgets){
|
||||
this.$invalidWidgets = $invalidWidgets;
|
||||
function UserForm() {
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
this.master = {
|
||||
@@ -30,11 +27,11 @@ detection, and preventing invalid form submission.
|
||||
}
|
||||
|
||||
UserForm.prototype = {
|
||||
cancel: function(){
|
||||
cancel: function() {
|
||||
this.form = angular.copy(this.master);
|
||||
},
|
||||
|
||||
save: function(){
|
||||
save: function() {
|
||||
this.master = this.form;
|
||||
this.cancel();
|
||||
}
|
||||
@@ -42,31 +39,34 @@ detection, and preventing invalid form submission.
|
||||
</script>
|
||||
<div ng:controller="UserForm">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="form.name" ng:required/> <br/><br/>
|
||||
<form name="myForm">
|
||||
|
||||
<label>Address:</label><br/>
|
||||
<input type="text" name="form.address.line1" size="33" ng:required/> <br/>
|
||||
<input type="text" name="form.address.city" size="12" ng:required/>,
|
||||
<input type="text" name="form.address.state" size="2" ng:required ng:validate="regexp:state"/>
|
||||
<input type="text" name="form.address.zip" size="5" ng:required
|
||||
ng:validate="regexp:zip"/><br/><br/>
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" ng:model="form.name" required/> <br/><br/>
|
||||
|
||||
<label>Contacts:</label>
|
||||
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in form.contacts">
|
||||
<select name="contact.type">
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" name="contact.value" ng:required/>
|
||||
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<button ng:click="cancel()" ng:disabled="{{master.$equals(form)}}">Cancel</button>
|
||||
<button ng:click="save()" ng:disabled="{{$invalidWidgets.visible() ||
|
||||
master.$equals(form)}}">Save</button>
|
||||
<label>Address:</label> <br/>
|
||||
<input type="text" ng:model="form.address.line1" size="33" required/> <br/>
|
||||
<input type="text" ng:model="form.address.city" size="12" required/>,
|
||||
<input type="text" ng:model="form.address.state" size="2"
|
||||
ng:pattern="state" required/>
|
||||
<input type="text" ng:model="form.address.zip" size="5"
|
||||
ng:pattern="zip" required/><br/><br/>
|
||||
|
||||
<label>Contacts:</label>
|
||||
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in form.contacts">
|
||||
<select ng:model="contact.type">
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" ng:model="contact.value" required/>
|
||||
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<button ng:click="cancel()" ng:disabled="{{master.$equals(form)}}">Cancel</button>
|
||||
<button ng:click="save()" ng:disabled="{{myForm.$invalid || master.$equals(form)}}">Save</button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
Debug View:
|
||||
@@ -75,7 +75,7 @@ master.$equals(form)}}">Save</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should enable save button', function(){
|
||||
it('should enable save button', function() {
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
@@ -84,13 +84,13 @@ master.$equals(form)}}">Save</button>
|
||||
element(':button:contains(Save)').click();
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
});
|
||||
it('should enable cancel button', function(){
|
||||
it('should enable cancel button', function() {
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
input('form.name').enter('change');
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Cancel)').click();
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
expect(element(':input[name=form.name]').val()).toEqual('John Smith');
|
||||
expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Resources - Buzz
|
||||
@description
|
||||
@@ -15,6 +14,7 @@ to retrieve Buzz activity and comments.
|
||||
<script>
|
||||
BuzzController.$inject = ['$resource'];
|
||||
function BuzzController($resource) {
|
||||
this.userId = 'googlebuzz';
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt: 'json', callback: 'JSON_CALLBACK'},
|
||||
@@ -32,7 +32,7 @@ to retrieve Buzz activity and comments.
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="BuzzController">
|
||||
<input name="userId" value="googlebuzz"/>
|
||||
<input ng:model="userId"/>
|
||||
<button ng:click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div class="buzz" ng:repeat="item in activities.data.items">
|
||||
@@ -53,7 +53,7 @@ to retrieve Buzz activity and comments.
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('fetch buzz and expand', function() {
|
||||
xit('fetch buzz and expand', function() {
|
||||
element(':button:contains(fetch)').click();
|
||||
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
|
||||
element('.buzz a:contains(Expand replies):first').click();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Deep Linking
|
||||
@description
|
||||
@@ -28,22 +27,22 @@ controller.
|
||||
|
||||
In this example we have a simple app which consist of two screens:
|
||||
|
||||
* Welcome: url `#` Show the user contact information.
|
||||
* Settings: url `#/settings` Show an edit screen for user contact information.
|
||||
* Welcome: url `welcome` Show the user contact information.
|
||||
* Settings: url `settings` Show an edit screen for user contact information.
|
||||
|
||||
|
||||
The two partials are defined in the following URLs:
|
||||
|
||||
* {@link ./examples/settings.html}
|
||||
* {@link ./examples/welcome.html}
|
||||
* <a href="./examples/settings.html" ng:ext-link>./examples/settings.html</a>
|
||||
* <a href="./examples/welcome.html" ng:ext-link>./examples/welcome.html</a>
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<doc:source jsfiddle="false">
|
||||
<script>
|
||||
AppCntl.$inject = ['$route']
|
||||
function AppCntl($route) {
|
||||
// define routes
|
||||
$route.when("", {template:'./examples/welcome.html', controller:WelcomeCntl});
|
||||
$route.when("/welcome", {template:'./examples/welcome.html', controller:WelcomeCntl});
|
||||
$route.when("/settings", {template:'./examples/settings.html', controller:SettingsCntl});
|
||||
$route.parent(this);
|
||||
|
||||
@@ -56,28 +55,30 @@ The two partials are defined in the following URLs:
|
||||
|
||||
function WelcomeCntl($route){}
|
||||
WelcomeCntl.prototype = {
|
||||
greet: function(){
|
||||
greet: function() {
|
||||
alert("Hello " + this.person.name);
|
||||
}
|
||||
};
|
||||
|
||||
function SettingsCntl(){
|
||||
SettingsCntl.$inject = ['$location'];
|
||||
function SettingsCntl($location){
|
||||
this.$location = $location;
|
||||
this.cancel();
|
||||
}
|
||||
SettingsCntl.prototype = {
|
||||
cancel: function(){
|
||||
cancel: function() {
|
||||
this.form = angular.copy(this.person);
|
||||
},
|
||||
|
||||
save: function(){
|
||||
save: function() {
|
||||
angular.copy(this.form, this.person);
|
||||
window.location.hash = "#";
|
||||
this.$location.path('/welcome');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="AppCntl">
|
||||
<h1>Your App Chrome</h1>
|
||||
[ <a href="#">Welcome</a> | <a href="#/settings">Settings</a> ]
|
||||
[ <a href="welcome">Welcome</a> | <a href="settings">Settings</a> ]
|
||||
<hr/>
|
||||
<span style="background-color: blue; color: white; padding: 3px;">
|
||||
Partial: {{$route.current.template}}
|
||||
@@ -87,7 +88,7 @@ The two partials are defined in the following URLs:
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should navigate to URL', function(){
|
||||
it('should navigate to URL', function() {
|
||||
element('a:contains(Welcome)').click();
|
||||
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
|
||||
element('a:contains(Settings)').click();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Form
|
||||
@description
|
||||
@@ -11,7 +10,7 @@ allow a user to enter data.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function FormController(){
|
||||
function FormController() {
|
||||
this.user = {
|
||||
name: 'John Smith',
|
||||
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
|
||||
@@ -24,25 +23,26 @@ allow a user to enter data.
|
||||
<div ng:controller="FormController" class="example">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="user.name" ng:required/> <br/><br/>
|
||||
<input type="text" ng:model="user.name" required/> <br/><br/>
|
||||
|
||||
<label>Address:</label><br/>
|
||||
<input type="text" name="user.address.line1" size="33" ng:required/> <br/>
|
||||
<input type="text" name="user.address.city" size="12" ng:required/>,
|
||||
<input type="text" name="user.address.state" size="2" ng:required ng:validate="regexp:state"/>
|
||||
<input type="text" name="user.address.zip" size="5" ng:required
|
||||
ng:validate="regexp:zip"/><br/><br/>
|
||||
<input type="text" ng:model="user.address.line1" size="33" required> <br/>
|
||||
<input type="text" ng:model="user.address.city" size="12" required>,
|
||||
<input type="text" ng:model="user.address.state" size="2"
|
||||
ng:pattern="state" required>
|
||||
<input type="text" ng:model="user.address.zip" size="5"
|
||||
ng:pattern="zip" required><br/><br/>
|
||||
|
||||
<label>Phone:</label>
|
||||
[ <a href="" ng:click="user.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in user.contacts">
|
||||
<select name="contact.type">
|
||||
<select ng:model="contact.type">
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
<option>pager</option>
|
||||
<option>IM</option>
|
||||
</select>
|
||||
<input type="text" name="contact.value" ng:required/>
|
||||
<input type="text" ng:model="contact.value" required/>
|
||||
[ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<hr/>
|
||||
@@ -52,35 +52,37 @@ ng:validate="regexp:zip"/><br/><br/>
|
||||
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should show debug', function(){
|
||||
it('should show debug', function() {
|
||||
expect(binding('user')).toMatch(/John Smith/);
|
||||
});
|
||||
it('should add contact', function(){
|
||||
it('should add contact', function() {
|
||||
using('.example').element('a:contains(add)').click();
|
||||
using('.example div:last').input('contact.value').enter('you@example.org');
|
||||
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
|
||||
expect(binding('user')).toMatch(/you@example.org/);
|
||||
});
|
||||
|
||||
it('should remove contact', function(){
|
||||
it('should remove contact', function() {
|
||||
using('.example').element('a:contains(X)').click();
|
||||
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
|
||||
});
|
||||
|
||||
it('should validate zip', function(){
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
.not().toMatch(/ng-validation-error/);
|
||||
it('should validate zip', function() {
|
||||
expect(using('.example').
|
||||
element(':input[ng\\:model="user.address.zip"]').
|
||||
prop('className')).not().toMatch(/ng-invalid/);
|
||||
using('.example').input('user.address.zip').enter('abc');
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
.toMatch(/ng-validation-error/);
|
||||
expect(using('.example').
|
||||
element(':input[ng\\:model="user.address.zip"]').
|
||||
prop('className')).toMatch(/ng-invalid/);
|
||||
});
|
||||
|
||||
it('should validate state', function(){
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
.not().toMatch(/ng-validation-error/);
|
||||
it('should validate state', function() {
|
||||
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
|
||||
.not().toMatch(/ng-invalid/);
|
||||
using('.example').input('user.address.state').enter('XXX');
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
.toMatch(/ng-validation-error/);
|
||||
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
|
||||
.toMatch(/ng-invalid/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -93,8 +95,8 @@ available in
|
||||
the {@link api/angular.scope scope} with the initial data.
|
||||
* For debugging purposes we have included a debug view of the model to better understand what
|
||||
is going on.
|
||||
* The {@link api/angular.widget.HTML input widgets} simply refer to the model and are auto bound.
|
||||
* The inputs {@link api/angular.validator validate}. (Try leaving them blank or entering non digits
|
||||
* The {@link api/angular.widget.input input widgets} simply refer to the model and are data-bound.
|
||||
* The inputs {@link guide/dev_guide.forms validate}. (Try leaving them blank or entering non digits
|
||||
in the zip field)
|
||||
* In your application you can simply read from or write to the model and the form will be updated.
|
||||
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Hello World
|
||||
@description
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" name="name" value="World"/>
|
||||
<hr/>
|
||||
Hello {{name}}!
|
||||
<script>
|
||||
function HelloCntl() {
|
||||
this.name = 'World';
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="HelloCntl">
|
||||
Your name: <input type="text" ng:model="name" value="World"/>
|
||||
<hr/>
|
||||
Hello {{name}}!
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should change the binding when user enters text', function(){
|
||||
it('should change the binding when user enters text', function() {
|
||||
expect(binding('name')).toEqual('World');
|
||||
input('name').enter('angular');
|
||||
expect(binding('name')).toEqual('angular');
|
||||
@@ -23,7 +29,7 @@
|
||||
Take a look through the source and note:
|
||||
|
||||
* The script tag that {@link guide/dev_guide.bootstrap bootstraps} the angular environment.
|
||||
* The text {@link api/angular.widget.HTML input widget} which is bound to the greeting name text.
|
||||
* The text {@link api/angular.widget.input input widget} which is bound to the greeting name text.
|
||||
* No need for listener registration and event firing on change events.
|
||||
* The implicit presence of the `name` variable which is in the root {@link api/angular.scope scope}.
|
||||
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: MVC
|
||||
@description
|
||||
@@ -26,7 +25,7 @@ no connection between the controller and the view.
|
||||
'cursor': 'pointer'
|
||||
};
|
||||
this.reset();
|
||||
this.$watch('$location.hashSearch.board', this.readUrl);
|
||||
this.$watch('$location.search().board', this.readUrl);
|
||||
}
|
||||
TicTacToeCntl.prototype = {
|
||||
dropPiece: function(row, col) {
|
||||
@@ -36,7 +35,7 @@ no connection between the controller and the view.
|
||||
this.setUrl();
|
||||
}
|
||||
},
|
||||
reset: function(){
|
||||
reset: function() {
|
||||
this.board = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
@@ -46,7 +45,7 @@ no connection between the controller and the view.
|
||||
this.winner = '';
|
||||
this.setUrl();
|
||||
},
|
||||
grade: function(){
|
||||
grade: function() {
|
||||
var b = this.board;
|
||||
this.winner =
|
||||
row(0) || row(1) || row(2) ||
|
||||
@@ -57,12 +56,12 @@ no connection between the controller and the view.
|
||||
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
|
||||
function same(a, b, c) { return (a==b && b==c) ? a : '';};
|
||||
},
|
||||
setUrl: function(){
|
||||
setUrl: function() {
|
||||
var rows = [];
|
||||
angular.forEach(this.board, function(row){
|
||||
rows.push(row.join(','));
|
||||
});
|
||||
this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove;
|
||||
this.$location.search({board: rows.join(';') + '/' + this.nextMove});
|
||||
},
|
||||
readUrl: function(scope, value) {
|
||||
if (value) {
|
||||
@@ -72,8 +71,6 @@ no connection between the controller and the view.
|
||||
this.board[col] = row.split(',');
|
||||
}, this);
|
||||
this.grade();
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -93,7 +90,7 @@ no connection between the controller and the view.
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should play a game', function(){
|
||||
it('should play a game', function() {
|
||||
piece(1, 1);
|
||||
expect(binding('nextMove')).toEqual('O');
|
||||
piece(3, 1);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Initializing Angular: Automatic Initiialization
|
||||
@name Developer Guide: Initializing Angular: Automatic Initialization
|
||||
@description
|
||||
|
||||
Angular initializes automatically when you load the angular script into your page, specifying
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Initializing Angular: Manual Initialization
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Initializing Angular
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Directives: Creating Custom Angular Directives
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding Angular Directives
|
||||
@description
|
||||
@@ -16,7 +15,7 @@ directives per element.
|
||||
You add angular directives to a standard HTML tag as in the following example, in which we have
|
||||
added the {@link api/angular.directive.ng:click ng:click} directive to a button tag:
|
||||
|
||||
<button name="button1" ng:click="foo()">Click This</button>
|
||||
<button ng:model="button1" ng:click="foo()">Click This</button>
|
||||
|
||||
In the example above, `name` is the standard HTML attribute, and `ng:click` is the angular
|
||||
directive. The `ng:click` directive lets you implement custom behavior in an associated controller
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Comparing Directives and Attribute Widgets
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Extending the Angular Compiler
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding Angular Markup
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Testing a New DOM Element
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding How the Compiler Works
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Widgets: Creating Custom Widgets
|
||||
@description
|
||||
@@ -61,7 +60,7 @@ angular.widget('@my:watch', function(expression, compileElement) {
|
||||
angular.widget('my:time', function(compileElement){
|
||||
compileElement.css('display', 'block');
|
||||
return function(linkElement){
|
||||
function update(){
|
||||
function update() {
|
||||
linkElement.text('Current time is: ' + new Date());
|
||||
setTimeout(update, 1000);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular HTML Compiler: Understanding Angular Widgets
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About Dependency Injection (DI)
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: DI: Understanding DI in Angular
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: DI: Using DI in Controllers
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Understanding Angular Expressions
|
||||
@description
|
||||
@@ -41,7 +40,7 @@ the `Scope:$eval()` method.
|
||||
1+2={{1+2}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function(){
|
||||
it('should calculate expression in binding', function() {
|
||||
expect(binding('1+2')).toEqual('3');
|
||||
});
|
||||
</doc:scenario>
|
||||
@@ -51,9 +50,15 @@ You can try evaluating different expressions here:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="exprs=[]" class="expressions">
|
||||
<script>
|
||||
function Cntl2() {
|
||||
this.exprs = [];
|
||||
this.expr = '3*10|currency';
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="Cntl2" class="expressions">
|
||||
Expression:
|
||||
<input type='text' name="expr" value="3*10|currency" size="80"/>
|
||||
<input type='text' ng:model="expr" size="80"/>
|
||||
<button ng:click="exprs.$add(expr)">Evaluate</button>
|
||||
<ul>
|
||||
<li ng:repeat="expr in exprs">
|
||||
@@ -64,7 +69,7 @@ You can try evaluating different expressions here:
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should allow user expression testing', function(){
|
||||
it('should allow user expression testing', function() {
|
||||
element('.expressions :button').click();
|
||||
var li = using('.expressions ul').repeater('li');
|
||||
expect(li.count()).toBe(1);
|
||||
@@ -84,13 +89,22 @@ the global state (a common source of subtle bugs).
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div class="example2" ng:init="$window = $service('$window')">
|
||||
Name: <input name="name" type="text" value="World"/>
|
||||
<button ng:click="($window.mockWindow || $window).alert('Hello ' + name)">Greet</button>
|
||||
<script>
|
||||
function Cntl1($window){
|
||||
this.name = 'World';
|
||||
|
||||
this.greet = function() {
|
||||
($window.mockWindow || $window).alert('Hello ' + this.name);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div class="example2" ng:controller="Cntl1">
|
||||
Name: <input ng:model="name" type="text"/>
|
||||
<button ng:click="greet()">Greet</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function(){
|
||||
it('should calculate expression in binding', function() {
|
||||
var alertText;
|
||||
this.addFutureAction('set mock', function($window, $document, done) {
|
||||
$window.mockWindow = {
|
||||
@@ -158,7 +172,7 @@ Extensions: You can further extend the expression vocabulary by adding new metho
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
Search: <input name="searchText"/>
|
||||
Search: <input ng:model="searchText"/>
|
||||
<table class="example3">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(searchText)">
|
||||
@@ -168,7 +182,7 @@ Extensions: You can further extend the expression vocabulary by adding new metho
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should filter the list', function(){
|
||||
it('should filter the list', function() {
|
||||
var tr = using('table.example3').repeater('tr.ng-attr-widget');
|
||||
expect(tr.count()).toBe(5);
|
||||
input('searchText').enter('a');
|
||||
|
||||
@@ -0,0 +1,610 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Forms
|
||||
@description
|
||||
|
||||
# Overview
|
||||
|
||||
Forms allow users to enter data into your application. Forms represent the bidirectional data
|
||||
bindings in Angular.
|
||||
|
||||
Forms consist of all of the following:
|
||||
|
||||
- the individual widgets with which users interact
|
||||
- the validation rules for widgets
|
||||
- the form, a collection of widgets that contains aggregated validation information
|
||||
|
||||
|
||||
# Form
|
||||
|
||||
A form groups a set of widgets together into a single logical data-set. A form is created using
|
||||
the {@link api/angular.widget.form <form>} element that calls the
|
||||
{@link api/angular.service.$formFactory $formFactory} service. The form is responsible for managing
|
||||
the widgets and for tracking validation information.
|
||||
|
||||
A form is:
|
||||
|
||||
- The collection which contains widgets or other forms.
|
||||
- Responsible for marshaling data from the model into a widget. This is
|
||||
triggered by {@link api/angular.scope.$watch $watch} of the model expression.
|
||||
- Responsible for marshaling data from the widget into the model. This is
|
||||
triggered by the widget emitting the `$viewChange` event.
|
||||
- Responsible for updating the validation state of the widget, when the widget emits
|
||||
`$valid` / `$invalid` event. The validation state is useful for controlling the validation
|
||||
errors shown to the user in it consist of:
|
||||
|
||||
- `$valid` / `$invalid`: Complementary set of booleans which show if a widget is valid / invalid.
|
||||
- `$error`: an object which has a property for each validation key emited by the widget.
|
||||
The value of the key is always true. If widget is valid, then the `$error`
|
||||
object has no properties. For example if the widget emits
|
||||
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
|
||||
updated to `$error.REQUIRED == true`.
|
||||
|
||||
- Responsible for aggregating widget validation information into the form.
|
||||
|
||||
- `$valid` / `$invalid`: Complementary set of booleans which show if all the child widgets
|
||||
(or forms) are valid or if any are invalid.
|
||||
- `$error`: an object which has a property for each validation key emited by the
|
||||
child widget. The value of the key is an array of widgets which fired the invalid
|
||||
event. If all child widgets are valid then, then the `$error` object has no
|
||||
properties. For example if a child widget emits
|
||||
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
|
||||
updated to `$error.REQUIRED == [ widgetWhichEmitedInvalid ]`.
|
||||
|
||||
|
||||
# Widgets
|
||||
|
||||
In Angular, a widget is the term used for the UI with which the user input. Examples of
|
||||
bult-in Angular widgets are {@link api/angular.widget.input input} and
|
||||
{@link api/angular.widget.select select}. Widgets provide the rendering and the user
|
||||
interaction logic. Widgets should be declared inside a form, if no form is provided an implicit
|
||||
form {@link api/angular.service.$formFactory $formFactory.rootForm} form is used.
|
||||
|
||||
Widgets are implemented as Angular controllers. A widget controller:
|
||||
|
||||
- implements methods:
|
||||
|
||||
- `$render` - Updates the DOM from the internal state as represented by `$viewValue`.
|
||||
- `$parseView` - Translate `$viewValue` to `$modelValue`. (`$modelValue` will be assigned to
|
||||
the model scope by the form)
|
||||
- `$parseModel` - Translate `$modelValue` to `$viewValue`. (`$viewValue` will be assigned to
|
||||
the DOM inside the `$render` method)
|
||||
|
||||
- responds to events:
|
||||
|
||||
- `$validate` - Emitted by the form when the form determines that the widget needs to validate
|
||||
itself. There may be more then one listener on the `$validate` event. The widget responds
|
||||
by emitting `$valid` / `$invalid` event of its own.
|
||||
|
||||
- emits events:
|
||||
|
||||
- `$viewChange` - Emitted when the user interacts with the widget and it is necessary to update
|
||||
the model.
|
||||
- `$valid` - Emitted when the widget determines that it is valid (usually as a response to
|
||||
`$validate` event or inside `$parseView()` or `$parseModel()` method).
|
||||
- `$invalid` - Emitted when the widget determines that it is invalid (usually as a response to
|
||||
`$validate` event or inside `$parseView()` or `$parseModel()` method).
|
||||
- `$destroy` - Emitted when the widget element is removed from the DOM.
|
||||
|
||||
|
||||
# CSS
|
||||
|
||||
Angular-defined widgets and forms set `ng-valid` and `ng-invalid` classes on themselves to allow
|
||||
the web-designer a way to style them. If you write your own widgets, then their `$render()`
|
||||
methods must set the appropriate CSS classes to allow styling.
|
||||
(See {@link dev_guide.templates.css-styling CSS})
|
||||
|
||||
|
||||
# Example
|
||||
|
||||
The following example demonstrates:
|
||||
|
||||
- How an error is displayed when a required field is empty.
|
||||
- Error highlighting.
|
||||
- How form submission is disabled when the form is invalid.
|
||||
- The internal state of the widget and form in the the 'Debug View' area.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<style>
|
||||
.ng-invalid { border: solid 1px red; }
|
||||
.ng-form {display: block;}
|
||||
</style>
|
||||
<script>
|
||||
function UserFormCntl() {
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
this.master = {
|
||||
customer: 'John Smith',
|
||||
address:{
|
||||
line1: '123 Main St.',
|
||||
city:'Anytown',
|
||||
state:'AA',
|
||||
zip:'12345'
|
||||
}
|
||||
};
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
UserFormCntl.prototype = {
|
||||
cancel: function() {
|
||||
this.form = angular.copy(this.master);
|
||||
},
|
||||
|
||||
save: function() {
|
||||
this.master = this.form;
|
||||
this.cancel();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="UserFormCntl">
|
||||
|
||||
<form name="userForm">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="customer" ng:model="form.customer" required/>
|
||||
<span class="error" ng:show="userForm.customer.$error.REQUIRED">
|
||||
Customer name is required!</span>
|
||||
<br/><br/>
|
||||
|
||||
<ng:form name="addressForm">
|
||||
<label>Address:</label> <br/>
|
||||
<input type="text" name="line1" size="33" required
|
||||
ng:model="form.address.line1"/> <br/>
|
||||
<input type="text" name="city" size="12" required
|
||||
ng:model="form.address.city"/>,
|
||||
<input type="text" name="state" ng:pattern="state" size="2" required
|
||||
ng:model="form.address.state"/>
|
||||
<input type="text" name="zip" ng:pattern="zip" size="5" required
|
||||
ng:model="form.address.zip"/><br/><br/>
|
||||
|
||||
<span class="error" ng:show="addressForm.$invalid">
|
||||
Incomplete address:
|
||||
<span class="error" ng:show="addressForm.state.$error.REQUIRED">
|
||||
Missing state!</span>
|
||||
<span class="error" ng:show="addressForm.state.$error.PATTERN">
|
||||
Invalid state!</span>
|
||||
<span class="error" ng:show="addressForm.zip.$error.REQUIRED">
|
||||
Missing zip!</span>
|
||||
<span class="error" ng:show="addressForm.zip.$error.PATTERN">
|
||||
Invalid zip!</span>
|
||||
</span>
|
||||
</ng:form>
|
||||
|
||||
<button ng:click="cancel()"
|
||||
ng:disabled="{{master.$equals(form)}}">Cancel</button>
|
||||
<button ng:click="save()"
|
||||
ng:disabled="{{userForm.$invalid || master.$equals(form)}}">
|
||||
Save</button>
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>form={{form}}</pre>
|
||||
<pre>master={{master}}</pre>
|
||||
<pre>userForm={{userForm}}</pre>
|
||||
<pre>addressForm={{addressForm}}</pre>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should enable save button', function() {
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.customer').enter('');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
input('form.customer').enter('change');
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Save)').click();
|
||||
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
|
||||
});
|
||||
it('should enable cancel button', function() {
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
input('form.customer').enter('change');
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
|
||||
element(':button:contains(Cancel)').click();
|
||||
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
|
||||
expect(element(':input[ng\\:model="form.customer"]').val()).toEqual('John Smith');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Life-cycle
|
||||
|
||||
- The `<form>` element triggers creation of a new form {@link dev_guide.scopes scope} using the
|
||||
{@link api/angular.service.$formFactory $formfactory}. The new form scope is added to the
|
||||
`<form>` element using the jQuery `.data()` method for later retrieval under the key `$form`.
|
||||
The form also sets up these listeners:
|
||||
|
||||
- `$destroy` - This event is emitted by nested widget when it is removed from the view. It gives
|
||||
the form a chance to clean up any validation references to the destroyed widget.
|
||||
- `$valid` / `$invalid` - This event is emitted by the widget on validation state change.
|
||||
|
||||
- `<input>` element triggers the creation of the widget using the
|
||||
{@link api/angular.service.$formFactory $formfactory.$createWidget()} method. The `$createWidget()`
|
||||
creates new widget instance by calling the current scope {@link api/angular.scope.$new .$new()} and
|
||||
registers these listeners:
|
||||
|
||||
- `$watch` on the model scope.
|
||||
- `$viewChange` event on the widget scope.
|
||||
- `$validate` event on the widget scope.
|
||||
- Element `change` event when the user enters data.
|
||||
|
||||
<img class="center" src="img/form_data_flow.png" border="1" />
|
||||
|
||||
|
||||
- When the user interacts with the widget:
|
||||
|
||||
1. The DOM element fires the `change` event which the widget intercepts. Widget then emits
|
||||
a `$viewChange` event which includes the new user-entered value. (Remember that the DOM events
|
||||
are outside of the Angular environment so the widget must emit its event within the
|
||||
{@link api/angular.scope.$apply $apply} method).
|
||||
2. The form's `$viewChange` listener copies the user-entered value to the widget's `$viewValue`
|
||||
property. Since the `$viewValue` is the raw value as entered by user, it may need to be
|
||||
translated to a different format/type (for example, translating a string to a number).
|
||||
If you need your widget to translate between the internal `$viewValue` and the external
|
||||
`$modelValue` state, you must declare a `$parseView()` method. The `$parseView()` method
|
||||
will copy `$viewValue` to `$modelValue` and perform any necessary translations.
|
||||
3. The `$modelValue` is written into the application model.
|
||||
4. The form then emits a `$validate` event, giving the widget's validators chance to validate the
|
||||
input. There can be any number of validators registered. Each validator may in turn
|
||||
emit a `$valid` / `$invalid` event with the validator's validation key. For example `REQUIRED`.
|
||||
5. Form listens to `$valid`/`$invalid` events and updates both the form as well as the widget
|
||||
scope with the validation state. The validation updates the `$valid` and `$invalid`, property
|
||||
as well as `$error` object. The widget's `$error` object is updated with the validation key
|
||||
such that `$error.REQUIRED == true` when the validation emits `$invalid` with `REQUIRED`
|
||||
validation key. Similarly the form's `$error` object gets updated, but instead of boolean
|
||||
`true` it contains an array of invalid widgets (widgets which fired `$invalid` event with
|
||||
`REQUIRED` validation key).
|
||||
|
||||
- When the model is updated:
|
||||
|
||||
1. The model `$watch` listener assigns the model value to `$modelValue` on the widget.
|
||||
2. The form then calls `$parseModel` method on widget if present. The method converts the
|
||||
value to renderable format and assigns it to `$viewValue` (for example converting number to a
|
||||
string.)
|
||||
3. The form then emits a `$validate` which behaves as described above.
|
||||
4. The form then calls `$render` method on the widget to update the DOM structure from the
|
||||
`$viewValue`.
|
||||
|
||||
|
||||
|
||||
# Writing Your Own Widget
|
||||
|
||||
This example shows how to implement a custom HTML editor widget in Angular.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function EditorCntl() {
|
||||
this.htmlContent = '<b>Hello</b> <i>World</i>!';
|
||||
}
|
||||
|
||||
function HTMLEditorWidget(element) {
|
||||
var self = this;
|
||||
var htmlFilter = angular.filter('html');
|
||||
|
||||
this.$parseModel = function() {
|
||||
// need to protect for script injection
|
||||
try {
|
||||
this.$viewValue = htmlFilter(
|
||||
this.$modelValue || '').get();
|
||||
if (this.$error.HTML) {
|
||||
// we were invalid, but now we are OK.
|
||||
this.$emit('$valid', 'HTML');
|
||||
}
|
||||
} catch (e) {
|
||||
// if HTML not parsable invalidate form.
|
||||
this.$emit('$invalid', 'HTML');
|
||||
}
|
||||
}
|
||||
|
||||
this.$render = function() {
|
||||
element.html(this.$viewValue);
|
||||
}
|
||||
|
||||
element.bind('keyup', function() {
|
||||
self.$apply(function() {
|
||||
self.$emit('$viewChange', element.html());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
angular.directive('ng:html-editor-model', function() {
|
||||
function linkFn($formFactory, element) {
|
||||
var exp = element.attr('ng:html-editor-model'),
|
||||
form = $formFactory.forElement(element),
|
||||
widget;
|
||||
element.attr('contentEditable', true);
|
||||
widget = form.$createWidget({
|
||||
scope: this,
|
||||
model: exp,
|
||||
controller: HTMLEditorWidget,
|
||||
controllerArgs: [element]});
|
||||
// if the element is destroyed, then we need to
|
||||
// notify the form.
|
||||
element.bind('$destroy', function() {
|
||||
widget.$destroy();
|
||||
});
|
||||
}
|
||||
linkFn.$inject = ['$formFactory'];
|
||||
return linkFn;
|
||||
});
|
||||
</script>
|
||||
<form name='editorForm' ng:controller="EditorCntl">
|
||||
<div ng:html-editor-model="htmlContent"></div>
|
||||
<hr/>
|
||||
HTML: <br/>
|
||||
<textarea ng:model="htmlContent" cols="80"></textarea>
|
||||
<hr/>
|
||||
<pre>editorForm = {{editorForm}}</pre>
|
||||
</form>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should enter invalid HTML', function() {
|
||||
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-valid/);
|
||||
input('htmlContent').enter('<');
|
||||
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-invalid/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
|
||||
# HTML Inputs
|
||||
|
||||
The most common widgets you will use will be in the form of the
|
||||
standard HTML set. These widgets are bound using the `name` attribute
|
||||
to an expression. In addition, they can have `required` attribute to further control their
|
||||
validation.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function Ctrl() {
|
||||
this.input1 = '';
|
||||
this.input2 = '';
|
||||
this.input3 = 'A';
|
||||
this.input4 = false;
|
||||
this.input5 = 'c';
|
||||
this.input6 = [];
|
||||
}
|
||||
</script>
|
||||
<table style="font-size:.9em;" ng:controller="Ctrl">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Format</th>
|
||||
<th>HTML</th>
|
||||
<th>UI</th>
|
||||
<th ng:non-bindable>{{input#}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>text</th>
|
||||
<td>String</td>
|
||||
<td><tt><input type="text" ng:model="input1"></tt></td>
|
||||
<td><input type="text" ng:model="input1" size="4"></td>
|
||||
<td><tt>{{input1|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>textarea</th>
|
||||
<td>String</td>
|
||||
<td><tt><textarea ng:model="input2"></textarea></tt></td>
|
||||
<td><textarea ng:model="input2" cols='6'></textarea></td>
|
||||
<td><tt>{{input2|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>radio</th>
|
||||
<td>String</td>
|
||||
<td><tt>
|
||||
<input type="radio" ng:model="input3" value="A"><br>
|
||||
<input type="radio" ng:model="input3" value="B">
|
||||
</tt></td>
|
||||
<td>
|
||||
<input type="radio" ng:model="input3" value="A">
|
||||
<input type="radio" ng:model="input3" value="B">
|
||||
</td>
|
||||
<td><tt>{{input3|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>checkbox</th>
|
||||
<td>Boolean</td>
|
||||
<td><tt><input type="checkbox" ng:model="input4"></tt></td>
|
||||
<td><input type="checkbox" ng:model="input4"></td>
|
||||
<td><tt>{{input4|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>pulldown</th>
|
||||
<td>String</td>
|
||||
<td><tt>
|
||||
<select ng:model="input5"><br>
|
||||
<option value="c">C</option><br>
|
||||
<option value="d">D</option><br>
|
||||
</select><br>
|
||||
</tt></td>
|
||||
<td>
|
||||
<select ng:model="input5">
|
||||
<option value="c">C</option>
|
||||
<option value="d">D</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><tt>{{input5|json}}</tt></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>multiselect</th>
|
||||
<td>Array</td>
|
||||
<td><tt>
|
||||
<select ng:model="input6" multiple size="4"><br>
|
||||
<option value="e">E</option><br>
|
||||
<option value="f">F</option><br>
|
||||
</select><br>
|
||||
</tt></td>
|
||||
<td>
|
||||
<select ng:model="input6" multiple size="4">
|
||||
<option value="e">E</option>
|
||||
<option value="f">F</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><tt>{{input6|json}}</tt></td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
|
||||
it('should exercise text', function() {
|
||||
input('input1').enter('Carlos');
|
||||
expect(binding('input1')).toEqual('"Carlos"');
|
||||
});
|
||||
it('should exercise textarea', function() {
|
||||
input('input2').enter('Carlos');
|
||||
expect(binding('input2')).toEqual('"Carlos"');
|
||||
});
|
||||
it('should exercise radio', function() {
|
||||
expect(binding('input3')).toEqual('"A"');
|
||||
input('input3').select('B');
|
||||
expect(binding('input3')).toEqual('"B"');
|
||||
input('input3').select('A');
|
||||
expect(binding('input3')).toEqual('"A"');
|
||||
});
|
||||
it('should exercise checkbox', function() {
|
||||
expect(binding('input4')).toEqual('false');
|
||||
input('input4').check();
|
||||
expect(binding('input4')).toEqual('true');
|
||||
});
|
||||
it('should exercise pulldown', function() {
|
||||
expect(binding('input5')).toEqual('"c"');
|
||||
select('input5').option('d');
|
||||
expect(binding('input5')).toEqual('"d"');
|
||||
});
|
||||
it('should exercise multiselect', function() {
|
||||
expect(binding('input6')).toEqual('[]');
|
||||
select('input6').options('e');
|
||||
expect(binding('input6')).toEqual('["e"]');
|
||||
select('input6').options('e', 'f');
|
||||
expect(binding('input6')).toEqual('["e","f"]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
#Testing
|
||||
|
||||
When unit-testing a controller it may be desirable to have a reference to form and to simulate
|
||||
different form validation states.
|
||||
|
||||
This example demonstrates a login form, where the login button is enabled only when the form is
|
||||
properly filled out.
|
||||
<pre>
|
||||
<div ng:controller="LoginController">
|
||||
<form name="loginForm">
|
||||
<input type="text" ng:model="username" required/>
|
||||
<input type="password" ng:model="password" required/>
|
||||
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
|
||||
</form>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
In the unit tests we do not have access to the DOM, and therefore the `loginForm` reference does
|
||||
not get set on the controller. This example shows how it can be unit-tested, by creating a mock
|
||||
form.
|
||||
<pre>
|
||||
function LoginController() {
|
||||
this.disableLogin = function() {
|
||||
return this.loginForm.$invalid;
|
||||
};
|
||||
}
|
||||
|
||||
describe('LoginController', function() {
|
||||
it('should disable login button when form is invalid', function() {
|
||||
var scope = angular.scope();
|
||||
var loginController = scope.$new(LoginController);
|
||||
|
||||
// In production the 'loginForm' form instance gets set from the view,
|
||||
// but in unit-test we have to set it manually.
|
||||
loginController.loginForm = scope.$service('$formFactory')();
|
||||
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
|
||||
// Now simulate an invalid form
|
||||
loginController.loginForm.$emit('$invalid', 'MyReason');
|
||||
expect(loginController.disableLogin()).toBe(true);
|
||||
|
||||
// Now simulate a valid form
|
||||
loginController.loginForm.$emit('$valid', 'MyReason');
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
## Custom widgets
|
||||
|
||||
This example demonstrates a login form, where the password has custom validation rules.
|
||||
<pre>
|
||||
<div ng:controller="LoginController">
|
||||
<form name="loginForm">
|
||||
<input type="text" ng:model="username" required/>
|
||||
<input type="@StrongPassword" ng:model="password" required/>
|
||||
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
|
||||
</form>
|
||||
</div>
|
||||
</pre>
|
||||
|
||||
In the unit tests we do not have access to the DOM, and therefore the `loginForm` and custom
|
||||
input type reference does not get set on the controller. This example shows how it can be
|
||||
unit-tested, by creating a mock form and a mock custom input type.
|
||||
<pre>
|
||||
function LoginController(){
|
||||
this.disableLogin = function() {
|
||||
return this.loginForm.$invalid;
|
||||
};
|
||||
|
||||
this.StrongPassword = function(element) {
|
||||
var widget = this;
|
||||
element.attr('type', 'password'); // act as password.
|
||||
this.$on('$validate', function(){
|
||||
widget.$emit(widget.$viewValue.length > 5 ? '$valid' : '$invalid', 'PASSWORD');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
describe('LoginController', function() {
|
||||
it('should disable login button when form is invalid', function() {
|
||||
var scope = angular.scope();
|
||||
var loginController = scope.$new(LoginController);
|
||||
var input = angular.element('<input>');
|
||||
|
||||
// In production the 'loginForm' form instance gets set from the view,
|
||||
// but in unit-test we have to set it manually.
|
||||
loginController.loginForm = scope.$service('$formFactory')();
|
||||
|
||||
// now instantiate a custom input type
|
||||
loginController.loginForm.$createWidget({
|
||||
scope: loginController,
|
||||
model: 'password',
|
||||
alias: 'password',
|
||||
controller: loginController.StrongPassword,
|
||||
controllerArgs: [input]
|
||||
});
|
||||
|
||||
// Verify that the custom password input type sets the input type to password
|
||||
expect(input.attr('type')).toEqual('password');
|
||||
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
|
||||
// Now simulate an invalid form
|
||||
loginController.loginForm.password.$emit('$invalid', 'PASSWORD');
|
||||
expect(loginController.disableLogin()).toBe(true);
|
||||
|
||||
// Now simulate a valid form
|
||||
loginController.loginForm.password.$emit('$valid', 'PASSWORD');
|
||||
expect(loginController.disableLogin()).toBe(false);
|
||||
|
||||
// Changing model state, should also influence the form validity
|
||||
loginController.password = 'abc'; // too short so it should be invalid
|
||||
scope.$digest();
|
||||
expect(loginController.loginForm.password.$invalid).toBe(true);
|
||||
|
||||
// Changeing model state, should also influence the form validity
|
||||
loginController.password = 'abcdef'; // should be valid
|
||||
scope.$digest();
|
||||
expect(loginController.loginForm.password.$valid).toBe(true);
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Introduction
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular
|
||||
@description
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular: Understanding the Controller Component
|
||||
@description
|
||||
|
||||
In angular, a controller is a JavaScript function (type/class) that is used to augment instances of
|
||||
In angular, a controller is a JavaScript function(type/class) that is used to augment instances of
|
||||
angular {@link dev_guide.scopes Scope}, excluding the root scope. When you or angular create a new
|
||||
child scope object via the {@link api/angular.scope.$new scope.$new} API , there is an
|
||||
option to pass in a controller as a method argument. This will tell angular to associate the
|
||||
@@ -68,7 +67,7 @@ Putting any presentation logic into controllers significantly affects testabilit
|
||||
logic. Angular offers {@link dev_guide.templates.databinding} for automatic DOM manipulation. If
|
||||
you have to perform your own manual DOM manipulation, encapsulate the presentation logic in {@link
|
||||
dev_guide.compiler.widgets widgets} and {@link dev_guide.compiler.directives directives}.
|
||||
- Input formatting — Use {@link dev_guide.templates.formatters angular formatters} instead.
|
||||
- Input formatting — Use {@link dev_guide.forms angular form widgets} instead.
|
||||
- Output filtering — Use {@link dev_guide.templates.filters angular filters} instead.
|
||||
- Run stateless or stateful code shared across controllers — Use {@link dev_guide.services angular
|
||||
services} instead.
|
||||
@@ -128,7 +127,7 @@ starts with capital letter and ends with "Ctrl" or "Controller".
|
||||
controller augments.
|
||||
- Assigning a property to `this` creates or updates the model.
|
||||
- Controller methods can be created through direct assignment to scope (the `chiliSpicy` method) or
|
||||
as prototype methods of the controller constructor function (the `jalapenoSpicy` method)
|
||||
as prototype methods of the controller constructor function(the `jalapenoSpicy` method)
|
||||
- Both controller methods are available in the template (for the `body` element and and its
|
||||
children).
|
||||
|
||||
@@ -139,7 +138,7 @@ previous example.
|
||||
|
||||
<pre>
|
||||
<body ng:controller="SpicyCtrl">
|
||||
<input name="customSpice" value="wasabi">
|
||||
<input ng:model="customSpice" value="wasabi">
|
||||
<button ng:click="spicy('chili')">Chili</button>
|
||||
<button ng:click="spicy(customSpice)">Custom spice</button>
|
||||
<p>The food is {{spice}} spicy!</p>
|
||||
@@ -227,7 +226,7 @@ Controller Test:
|
||||
<pre>
|
||||
describe('myController function', function() {
|
||||
|
||||
describe('myController', function(){
|
||||
describe('myController', function() {
|
||||
var ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular: Understanding the Model Component
|
||||
@description
|
||||
@@ -41,7 +40,7 @@ when processing the following template constructs:
|
||||
|
||||
* Form input, select, textarea and other form elements:
|
||||
|
||||
<input name="query" value="fluffy cloud">
|
||||
<input ng:model="query" value="fluffy cloud">
|
||||
|
||||
The code above creates a model called "query" on the current scope with the value set to "fluffy
|
||||
cloud".
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: About MVC in Angular: Understanding the View Component
|
||||
@description
|
||||
|
||||
@@ -42,23 +42,31 @@ easier a web developer's life can if they're using angular:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<b>Invoice:</b>
|
||||
<br />
|
||||
<br />
|
||||
<table>
|
||||
<tr><td> </td><td> </td>
|
||||
<tr><td>Quantity</td><td>Cost</td></tr>
|
||||
<tr>
|
||||
<td><input name="qty" value="1" ng:validate="integer:0" ng:required /></td>
|
||||
<td><input name="cost" value="19.95" ng:validate="number" ng:required /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<b>Total:</b> {{qty * cost | currency}}
|
||||
<script>
|
||||
function InvoiceCntl() {
|
||||
this.qty = 1;
|
||||
this.cost = 19.95;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="InvoiceCntl">
|
||||
<b>Invoice:</b>
|
||||
<br />
|
||||
<br />
|
||||
<table>
|
||||
<tr><td> </td><td> </td>
|
||||
<tr><td>Quantity</td><td>Cost</td></tr>
|
||||
<tr>
|
||||
<td><input type="integer" min="0" ng:model="qty" required ></td>
|
||||
<td><input type="number" ng:model="cost" required ></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<b>Total:</b> {{qty * cost | currency}}
|
||||
</div>
|
||||
</doc:source>
|
||||
<!--
|
||||
<doc:scenario>
|
||||
it('should show of angular binding', function(){
|
||||
it('should show of angular binding', function() {
|
||||
expect(binding('qty * cost')).toEqual('$19.95');
|
||||
input('qty').enter('2');
|
||||
input('cost').enter('5.00');
|
||||
@@ -89,18 +97,18 @@ In the `<script>` tag we do two angular setup tasks:
|
||||
From the `name` attribute of the `<input>` tags, angular automatically sets up two-way data
|
||||
binding, and we also demonstrate some easy input validation:
|
||||
|
||||
Quantity: <input name="qty" value="1" ng:validate="integer:0" ng:required/>
|
||||
Cost: <input name="cost" value="199.95" ng:validate="number" ng:required/>
|
||||
Quantity: <input type="integer" min="0" ng:model="qty" required >
|
||||
Cost: <input type="number" ng:model="cost" required >
|
||||
|
||||
These input widgets look normal enough, but consider these points:
|
||||
|
||||
* When this page loaded, angular bound the names of the input widgets (`qty` and `cost`) to
|
||||
variables of the same name. Think of those variables as the "Model" component of the
|
||||
Model-View-Controller design pattern.
|
||||
* Note the angular directives, {@link api/angular.widget.@ng:validate ng:validate} and {@link
|
||||
api/angular.widget.@ng:required ng:required}. You may have noticed that when you enter invalid data
|
||||
* Note the angular/HTML widget, {@link api/angular.widget.input input}.
|
||||
You may have noticed that when you enter invalid data
|
||||
or leave the the input fields blank, the borders turn red color, and the display value disappears.
|
||||
These `ng:` directives make it easier to implement field validators than coding them in JavaScript,
|
||||
These widgets make it easier to implement field validation than coding them in JavaScript,
|
||||
no? Yes.
|
||||
|
||||
And finally, the mysterious `{{ double curly braces }}`:
|
||||
|
||||
@@ -0,0 +1,642 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Using $location
|
||||
@description
|
||||
|
||||
# What does it do?
|
||||
|
||||
The `$location` service parses the URL in the browser address bar (based on the {@link
|
||||
https://developer.mozilla.org/en/window.location window.location}) and makes the URL available to
|
||||
your application. Changes to the URL in the address bar are reflected into $location service and
|
||||
changes to $location are reflected into the browser address bar.
|
||||
|
||||
**The $location service:**
|
||||
|
||||
- Exposes the current URL in the browser address bar, so you can
|
||||
- Watch and observe the URL.
|
||||
- Change the URL.
|
||||
- Synchronizes the URL with the browser when the user
|
||||
- Changes the address bar.
|
||||
- Clicks the back or forward button (or clicks a History link).
|
||||
- Clicks on a link.
|
||||
- Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
|
||||
|
||||
|
||||
## Comparing $location to window.location
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<td class="empty-corner-lt"></td>
|
||||
<td>window.location</td>
|
||||
<td>$location service</td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="head">purpose</td>
|
||||
<td>allow read/write access to the current browser location</td>
|
||||
<td>same</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">API</td>
|
||||
<td>exposes "raw" object with properties that can be directly modified</td>
|
||||
<td>exposes jQuery-style getters and setters</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">integration with angular application life-cycle</td>
|
||||
<td>none</td>
|
||||
<td>knows about all internal life-cycle phases, integrates with $watch, ...</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">seamless integration with HTML5 API</td>
|
||||
<td>no</td>
|
||||
<td>yes (with a fallback for legacy browsers)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">aware of docroot/context from which the application is loaded</td>
|
||||
<td>no - window.location.path returns "/docroot/actual/path"</td>
|
||||
<td>yes - $location.path() returns "/actual/path"</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## When should I use $location?
|
||||
Any time your application needs to react to a change in the current URL or if you want to change
|
||||
the current URL in the browser.
|
||||
|
||||
## What does it not do?
|
||||
Does not cause a full page reload when the browser URL is changed. To reload the page after
|
||||
changing the URL, use the lower-level API, `$window.location.href`.
|
||||
|
||||
|
||||
# General overview of the API
|
||||
|
||||
The `$location` service can behave differently, depending on the configuration that was provided to
|
||||
it when it was instantiated. The default configuration is suitable for many applications, for
|
||||
others customizing the configuration can enable new features.
|
||||
|
||||
Once the `$location` service is instantiated, you can interact with it via jQuery-style getter and
|
||||
setter methods that allow you to get or change the current URL in the browser.
|
||||
|
||||
## $location service configuration
|
||||
|
||||
To configure the `$location` service, you define the `$locationConfig` service which is an object
|
||||
with configuration properties:
|
||||
|
||||
- **html5Mode**: {boolean}<br />
|
||||
`true` - see HTML5 mode<br />
|
||||
`false` - see Hashbang mode<br />
|
||||
default: `false`
|
||||
|
||||
- **hashPrefix**: {string}<br />
|
||||
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
|
||||
default: `'!'`
|
||||
|
||||
### Example configuration
|
||||
<pre>
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
</pre>
|
||||
|
||||
## Getter and setter methods
|
||||
|
||||
`$location` service provides getter methods for read-only parts of the URL (absUrl, protocol, host,
|
||||
port) and getter / setter methods for url, path, search, hash:
|
||||
<pre>
|
||||
// get the current path
|
||||
$location.path();
|
||||
|
||||
// change the path
|
||||
$location.path('/newValue')
|
||||
</pre>
|
||||
|
||||
All of the setter methods return the same `$location` object to allow chaining. For example, to
|
||||
change multiple segments in one go, chain setters like this:
|
||||
<pre>$location.path('/newValue').search({key: value});</pre>
|
||||
|
||||
There is a special `replace` method which can be used to tell the $location service that the next
|
||||
time the $location service is synced with the browser, the last history record should be replaced
|
||||
instead of creating a new one. This is useful when you want to implement redirection, which would
|
||||
otherwise break the back button (navigating back would retrigger the redirection). To change the
|
||||
current URL without creating a new browser history record you can call:
|
||||
<pre>
|
||||
$location.path('/someNewPath');
|
||||
$location.replace();
|
||||
// or you can chain these as: $location.path('/someNewPath').replace();
|
||||
</pre>
|
||||
|
||||
Note that the setters don't update `window.location` immediately. Instead, `$location` service is
|
||||
aware of the {@link api/angular.scope scope} life-cycle and coalesces multiple `$location`
|
||||
mutations into one "commit" to the `window.location` object during the scope `$digest` phase. Since
|
||||
multiple changes to the $location's state will be pushed to the browser as a single change, it's
|
||||
enough to call the `replace()` method just once to make the entire "commit" a replace operation
|
||||
rather than addition to the browser history. Once the browser is updated, the $location service
|
||||
resets the flag set by `replace()` method and future mutations will create new history records,
|
||||
unless `replace()` is called again.
|
||||
|
||||
### Setters and character encoding
|
||||
You can pass special characters to `$location` service and it will encode them according to rules
|
||||
specified in {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. When you access the methods:
|
||||
|
||||
- All values that are passed to `$location` setter methods, `path()`, `search()`, `hash()`, are
|
||||
encoded.
|
||||
- Getters (calls to methods without parameters) return decoded values for the following methods
|
||||
`path()`, `search()`, `hash()`.
|
||||
- When you call the `absUrl()` method, the returned value is a full url with its segments encoded.
|
||||
- When you call the `url()` method, the returned value is path, search and hash, in the form
|
||||
`/path?search=a&b=c#hash`. The segments are encoded as well.
|
||||
|
||||
|
||||
# Hashbang and HTML5 Modes
|
||||
|
||||
`$location` service has two configuration modes which control the format of the URL in the browser
|
||||
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
|
||||
HTML5 {@link http://www.w3.org/TR/html5/history.html History API}. Applications use the same API in
|
||||
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
|
||||
facilitate the browser URL change and history management.
|
||||
|
||||
<img src="img/guide/hashbang_vs_regular_url.jpg">
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<td class="empty-corner-lt"></td>
|
||||
<td>Hashbang mode</td>
|
||||
<td>HTML5 mode</td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="head">configuration</td>
|
||||
<td>the default</td>
|
||||
<td>{ html5Mode: true }</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">URL format</td>
|
||||
<td>hashbang URLs in all browsers</td>
|
||||
<td>regular URLs in modern browser, hashbang URLs in old browser</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head"><a href=""> link rewriting</td>
|
||||
<td>no</td>
|
||||
<td>yes</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="head">requires server-side configuration</td>
|
||||
<td>no</td>
|
||||
<td>yes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Hashbang mode (default mode)
|
||||
|
||||
In this mode, `$location` uses Hashbang URLs in all browsers.
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5Mode: false,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
// open http://host.com/base/index.html#!/a
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/a'
|
||||
$location.path() == '/a'
|
||||
|
||||
$location.path('/foo')
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/foo'
|
||||
|
||||
$location.search() == {}
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/foo?a=b&c'
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.absUrl() == 'http://host.com/base/index.html#!/new?x=y'
|
||||
</pre>
|
||||
|
||||
### Crawling your app
|
||||
|
||||
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
|
||||
your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
|
||||
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
|
||||
see {@link http://code.google.com/web/ajaxcrawling/docs/specification.html Making AJAX Applications
|
||||
Crawlable}.
|
||||
|
||||
## HTML5 mode
|
||||
|
||||
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
|
||||
through the HTML5 history API, which allows for use of regular URL path and search segments,
|
||||
instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the
|
||||
`$location` service will fall back to using the hashbang URLs automatically. This frees you from
|
||||
having to worry about whether the browser displaying your app supports the history API or not; the
|
||||
`$location` service transparently uses the best available option.
|
||||
|
||||
- Opening a regular URL in a legacy browser -> redirects to a hashbang URL
|
||||
- Opening hashbang URL in a modern browser -> rewrites to a regular URL
|
||||
|
||||
### Example
|
||||
|
||||
<pre>
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5Mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
// in browser with HTML5 history support:
|
||||
// open http://host.com/#!/a -> rewrite to http://host.com/a
|
||||
// (replacing the http://host.com/#!/a history record)
|
||||
$location.path() == '/a'
|
||||
|
||||
$location.path('/foo');
|
||||
$location.absUrl() == 'http://host.com/foo'
|
||||
|
||||
$location.search() == {}
|
||||
$location.search({a: 'b', c: true});
|
||||
$location.absUrl() == 'http://host.com/foo?a=b&c'
|
||||
|
||||
$location.path('/new').search('x=y');
|
||||
$location.url() == 'new?x=y'
|
||||
$location.absUrl() == 'http://host.com/new?x=y'
|
||||
|
||||
// in browser without html5 history support:
|
||||
// open http://host.com/new?x=y -> redirect to http://host.com/#!/new?x=y
|
||||
// (again replacing the http://host.com/new?x=y history item)
|
||||
$location.path() == '/new'
|
||||
$location.search() == {x: 'y'}
|
||||
|
||||
$location.path('/foo/bar');
|
||||
$location.path() == '/foo/bar'
|
||||
$location.url() == '/foo/bar?x=y'
|
||||
$location.absUrl() == 'http://host.com/#!/foo/bar?x=y'
|
||||
</pre>
|
||||
|
||||
### Fallback for legacy browsers
|
||||
|
||||
For browsers that support the HTML5 history API, `$location` uses the HTML5 history API to write
|
||||
path and search. If the history API is not supported by a browser, `$location` supplies a Hasbang
|
||||
URL. This frees you from having to worry about whether the browser viewing your app supports the
|
||||
history API or not; the `$location` service makes this transparent to you.
|
||||
|
||||
### Html link rewriting
|
||||
|
||||
When you use the history API mode, you will need different links in different browser, but all you
|
||||
have to do is specify regular URL links, such as: `<a href="/some?foo=bar">link</a>`
|
||||
|
||||
When a user clicks on this link,
|
||||
|
||||
- In a legacy browser, the URL changes to `/index.html#!/some?foo=bar`
|
||||
- In a modern browser, the URL changes to `/some?foo=bar`
|
||||
|
||||
|
||||
In cases like the following, links are not rewritten; instead, the browser will perform a full page
|
||||
reload to the original link.
|
||||
|
||||
- Links with an `ng:ext-link` directive<br />
|
||||
Example: `<a href="/ext/link?a=b" ng:ext-link>link</a>`
|
||||
- Links that contain `target="_blank"`<br />
|
||||
Example: `<a href="/ext/link?a=b" target="_blank">link</a>`
|
||||
- Absolute links that go to a different domain<br />
|
||||
Example: `<a href="http://angularjs.org/">link</a>`
|
||||
|
||||
### Server side
|
||||
|
||||
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
|
||||
to entry point of your application (e.g. index.html)
|
||||
|
||||
### Crawling your app
|
||||
|
||||
If you want your AJAX application to be indexed by web crawlers, you rill need to add the following
|
||||
meta tag to the HEAD section of your document:
|
||||
<pre><meta name="fragment" content="!" /></pre>
|
||||
|
||||
This statement causes a crawler to request links with empty `_escaped_fragment_` parameter so that
|
||||
your server can recognize the crawler and serve it HTML snapshots. For more information about this
|
||||
technique, see {@link http://code.google.com/web/ajaxcrawling/docs/specification.html Making AJAX
|
||||
Applications Crawlable}.
|
||||
|
||||
### Relative links
|
||||
|
||||
Be sure to check all relative links, images, scripts etc. You must use an absolute path because the
|
||||
path is going to be rewritten. You can use `<base href="" />` tag as well.
|
||||
|
||||
Running Angular apps with the History API enabled from document root is strongly encouraged as it
|
||||
takes care of all relative link issues. **Otherwise you have to specify <base href="" /> !**
|
||||
|
||||
### Sending links among different browsers
|
||||
|
||||
Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in
|
||||
legacy browsers and hashbang links in modern browser:
|
||||
|
||||
- Modern browser will rewrite hashbang URLs to regular URLs.
|
||||
- Older browsers will redirect regular URLs to hashbang URLs.
|
||||
|
||||
### Example
|
||||
|
||||
Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so
|
||||
that you can see the differences. These `$location` services are connected to a fake browsers. Each
|
||||
input represents address bar of the browser.
|
||||
|
||||
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
|
||||
redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
|
||||
= on page reload.
|
||||
|
||||
In this examples we use `<base href="/base/index.html" />`
|
||||
|
||||
<ul class="doc-example">
|
||||
<li ng:non-bindable class="html5-hashbang-example">
|
||||
<div id="html5-mode" ng:controller="Html5Cntl">
|
||||
<h3>Browser with History API</h3>
|
||||
<ng:address-bar browser="html5"></ng:address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> | <a
|
||||
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
|
||||
ng:ext-link>external</a>
|
||||
</div>
|
||||
|
||||
<div id="hashbang-mode" ng:controller="HashbangCntl">
|
||||
<h3>Browser without History API</h3>
|
||||
<ng:address-bar browser="hashbang"></ng:address-bar><br /><br />
|
||||
$location.protocol() = {{$location.protocol()}}<br />
|
||||
$location.host() = {{$location.host()}}<br />
|
||||
$location.port() = {{$location.port()}}<br />
|
||||
$location.path() = {{$location.path()}}<br />
|
||||
$location.search() = {{$location.search()}}<br />
|
||||
$location.hash() = {{$location.hash()}}<br />
|
||||
<a href="/base/first?a=b">/base/first?a=b</a> | <a
|
||||
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
|
||||
ng:ext-link>external</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">
|
||||
function FakeBrowser(initUrl, baseHref) {
|
||||
this.onUrlChange = function(fn) {
|
||||
this.urlChange = fn;
|
||||
};
|
||||
|
||||
this.url = function() {
|
||||
return initUrl;
|
||||
};
|
||||
|
||||
this.defer = function(fn, delay) {
|
||||
setTimeout(function() { fn(); }, delay || 0);
|
||||
};
|
||||
|
||||
this.baseHref = function() {
|
||||
return baseHref;
|
||||
};
|
||||
|
||||
this.hover = angular.noop;
|
||||
this.notifyWhenOutstandingRequests = angular.noop;
|
||||
}
|
||||
|
||||
var browsers = {
|
||||
html5: new FakeBrowser('http://www.host.com/base/path?a=b#h', '/base/index.html'),
|
||||
hashbang: new FakeBrowser('http://www.host.com/base/index.html#!/path?a=b#h',
|
||||
'/base/index.html')
|
||||
};
|
||||
|
||||
function Html5Cntl($location) {
|
||||
this.$location = $location;
|
||||
}
|
||||
|
||||
function HashbangCntl($location) {
|
||||
this.$location = $location;
|
||||
}
|
||||
|
||||
angular.widget('ng:address-bar', function(tpl) {
|
||||
return function(elm) {
|
||||
var browser = browsers[elm.attr('browser')],
|
||||
input = angular.element('<input type="text" />').val(browser.url()),
|
||||
delay;
|
||||
|
||||
input.bind('keypress keyup keydown', function() {
|
||||
if (!delay) {
|
||||
delay = setTimeout(fireUrlChange, 250);
|
||||
}
|
||||
});
|
||||
|
||||
browser.url = function(url) {
|
||||
return input.val(url);
|
||||
};
|
||||
|
||||
elm.append('Address: ').append(input);
|
||||
|
||||
function fireUrlChange() {
|
||||
delay = null;
|
||||
browser.urlChange(input.val());
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function initEnv(name) {
|
||||
var root = angular.element(document.getElementById(name + '-mode'));
|
||||
var scope = angular.scope(null, {
|
||||
$locationConfig: {html5Mode: true, hashPrefix: '!'},
|
||||
$browser: browsers[name],
|
||||
$document: root,
|
||||
$sniffer: {history: name == 'html5'}
|
||||
});
|
||||
|
||||
angular.compile(root)(scope).$apply();
|
||||
root.bind('click', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
initEnv('html5');
|
||||
initEnv('hashbang');
|
||||
</script>
|
||||
|
||||
|
||||
# Caveats
|
||||
|
||||
## Page reload navigation
|
||||
|
||||
The `$location` service allows you to change only the URL; it does not allow you to reload the
|
||||
page. When you need to change the URL and reload the page or navigate to a different page, please
|
||||
use a lower level API, {@link api/angular.service.$window $window.location.href}.
|
||||
|
||||
## Using $location outside of the scope life-cycle
|
||||
|
||||
`$location` knows about Angular's {@link api/angular.scope scope} life-cycle. When a URL changes in
|
||||
the browser it updates the `$location` and calls `$apply` so that all $watchers / $observers are
|
||||
notified.
|
||||
When you change the `$location` inside the `$digest` phase everything is ok; `$location` will
|
||||
propagate this change into browser and will notify all the $watchers / $observers.
|
||||
When you want to change the `$location` from outside Angular (for example, through a DOM Event or
|
||||
during testing) - you must call `$apply` to propagate the changes.
|
||||
|
||||
## $location.path() and ! or / prefixes
|
||||
|
||||
A path should always begin with forward slash (`/`); the `$location.path()` setter will add the
|
||||
forward slash if it is missing.
|
||||
|
||||
Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually
|
||||
hashPrefix.
|
||||
|
||||
|
||||
# Testing with the $location service
|
||||
|
||||
When using `$location` service during testing, you are outside of the angular's {@link
|
||||
api/angular.scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
|
||||
|
||||
<pre>
|
||||
angular.service('$serviceUnderTest', function($location) {
|
||||
// whatever it does...
|
||||
};
|
||||
|
||||
describe('$serviceUnderTest', function() {
|
||||
var scope, $location, $sut;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = angular.scope();
|
||||
$location = scope.$service('$location');
|
||||
$sut = scope.$service('$serviceUnderTest');
|
||||
});
|
||||
|
||||
it('should...', function() {
|
||||
$location.path('/new/path');
|
||||
scope.$apply();
|
||||
|
||||
// test whatever the service should do...
|
||||
|
||||
});
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Migrating from earlier AngularJS releases
|
||||
|
||||
In earlier releases of Angular, `$location` used `hashPath` or `hashSearch` to process path and
|
||||
search methods. With this release, the `$location` service processes path and search methods and
|
||||
then uses the information it obtains to compose hashbang URLs (such as
|
||||
`http://server.com/#!/path?search=a`), when necessary.
|
||||
|
||||
## Changes to your code
|
||||
|
||||
<table>
|
||||
<tr class="head">
|
||||
<td>Navigation inside the app</td>
|
||||
<td>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.hash = value<br />$location.update(value)<br
|
||||
/>$location.updateHash(value)</td>
|
||||
<td>$location.path(path).search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath = path</td>
|
||||
<td>$location.path(path)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch = search</td>
|
||||
<td>$location.search(search)</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Navigation outside the app</td>
|
||||
<td>Use lower level API</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href = value<br />$location.update(value)</td>
|
||||
<td>$window.location.href = value</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location[protocol | host | port | path | search]</td>
|
||||
<td>$window.location[protocol | host | port | path | search]</td>
|
||||
</tr>
|
||||
|
||||
<tr class="head">
|
||||
<td>Read access</td>
|
||||
<td>Change to</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashPath</td>
|
||||
<td>$location.path()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.hashSearch</td>
|
||||
<td>$location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.href<br />$location.protocol<br />$location.host<br />$location.port<br
|
||||
/>$location.hash</td>
|
||||
<td>$location.absUrl()<br />$location.protocol()<br />$location.host()<br />$location.port()<br
|
||||
/>$location.path() + $location.search()</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>$location.path<br />$location.search</td>
|
||||
<td>$window.location.path<br />$window.location.search</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Two-way binding to $location
|
||||
|
||||
The Angular's compiler currently does not support two-way binding for methods (see {@link
|
||||
https://github.com/angular/angular.js/issues/404 issue}). If you should require two-way binding,
|
||||
you will need to specify an extra property that has two watchers. For example:
|
||||
<pre>
|
||||
<!-- html -->
|
||||
<input type="text" ng:model="locationPath" />
|
||||
</pre>
|
||||
<pre>
|
||||
// js - controller
|
||||
this.$watch('locationPath', function(scope, path) {
|
||||
$location.path(path);
|
||||
});
|
||||
|
||||
this.$watch('$location.path()', function(scope, path) {
|
||||
scope.locationPath = path;
|
||||
});
|
||||
</pre>
|
||||
|
||||
|
||||
# Related API
|
||||
|
||||
* {@link api/angular.service.$location $location API}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Creating Angular Services
|
||||
@description
|
||||
@@ -10,7 +9,7 @@ that angular's DI will use to create the service object when it is needed.
|
||||
The `angular.service` method accepts three parameters:
|
||||
|
||||
- `{string} name` - Name of the service.
|
||||
- `{function()} factory` - Factory function (called just once by DI).
|
||||
- `{function()} factory` - Factory function(called just once by DI).
|
||||
- `{Object} config` - Configuration object with the following properties:
|
||||
- `$inject` - {Array.<string>} - Array of service ids this service depends on. These services
|
||||
will be passed as arguments into the factory function in the same order specified in the `$inject`
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Injecting Services Into Controllers
|
||||
@description
|
||||
@@ -54,13 +53,13 @@ myController.$inject = ['notify'];
|
||||
|
||||
<div ng:controller="myController">
|
||||
<p>Let's try this simple notify service, injected into the controller...</p>
|
||||
<input ng:init="message='test'" type="text" name="message" />
|
||||
<input ng:init="message='test'" type="text" ng:model="message" />
|
||||
<button ng:click="callNotify(message);">NOTIFY</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should test service', function(){
|
||||
expect(element(':input[name=message]').val()).toEqual('test');
|
||||
it('should test service', function() {
|
||||
expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Managing Service Dependencies
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Registering Angular Services
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Testing Angular Services
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Angular Services: Understanding Angular Services
|
||||
@description
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Working With CSS in Angular
|
||||
@description
|
||||
|
||||
|
||||
Angular sets these CSS classes. It is up to your application to provide useful styling.
|
||||
|
||||
# CSS classes used by angular
|
||||
|
||||
* `ng-invalid`, `ng-valid`
|
||||
- **Usage:** angular applies this class to an input widget element if that element's input does
|
||||
notpass validation. (see {@link api/angular.widget.input input} widget).
|
||||
|
||||
* `ng-pristine`, `ng-dirty`
|
||||
- **Usage:** angular {@link api/angular.widget.input input} widget applies `ng-pristine` class
|
||||
to a new input widget element which did not have user interaction. Once the user interacts with
|
||||
the input widget the class is changed to `ng-dirty`.
|
||||
|
||||
# Marking CSS classes
|
||||
|
||||
* `ng-widget`, `ng-directive`
|
||||
- **Usage:** angular sets these class on elements where {@link api/angular.widget widget} or
|
||||
{@link api/angular.directive directive} has bound to.
|
||||
|
||||
|
||||
* Old browser support
|
||||
- Pre v9, IE browsers could not select `ng:include` elements in CSS, because of the `:`
|
||||
character. For this reason angular also sets `ng-include` class on any element which has `:`
|
||||
character in the name by replacing `:` with `-`.
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates Angular Templates}
|
||||
* {@link dev_guide.forms Angular Forms}
|
||||
@@ -1,52 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Working With CSS in Angular
|
||||
@description
|
||||
|
||||
|
||||
Angular includes built-in CSS classes, which in turn have predefined CSS styles.
|
||||
|
||||
# Built-in CSS classes
|
||||
|
||||
* `ng-exception`
|
||||
|
||||
**Usage:** angular applies this class to a DOM element if that element contains an Expression that
|
||||
threw an exception when evaluated.
|
||||
|
||||
**Styling:** The built-in styling of the ng-exception class displays an error message surrounded
|
||||
by a solid red border, for example:
|
||||
|
||||
<div class="ng-exception">Error message</div>
|
||||
|
||||
You can try to evaluate malformed expressions in {@link dev_guide.expressions expressions} to see
|
||||
the `ng-exception` class' styling.
|
||||
|
||||
* `ng-validation-error`
|
||||
|
||||
**Usage:** angular applies this class to an input widget element if that element's input does not
|
||||
pass validation. Note that you set the validation criteria on the input widget element using the
|
||||
Ng:validate or Ng:required directives.
|
||||
|
||||
**Styling:** The built-in styling of the ng-validation-error class turns the border of the input
|
||||
box red and includes a hovering UI element that includes more details of the validation error. You
|
||||
can see an example in {@link api/angular.widget.@ng:validate ng:validate example}.
|
||||
|
||||
## Overriding Styles for Angular CSS Classes
|
||||
|
||||
To override the styles for angular's built-in CSS classes, you can do any of the following:
|
||||
|
||||
* Download the source code, edit angular.css, and host the source on your own server.
|
||||
* Create a local CSS file, overriding any styles that you'd like, and link to it from your HTML file
|
||||
as you normally would:
|
||||
|
||||
<pre>
|
||||
<link href="yourfile.css" rel="stylesheet" type="text/css">
|
||||
</pre>
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates Angular Templates}
|
||||
* {@link dev_guide.templates.css Working With CSS in Angular}
|
||||
* {@link dev_guide.templates.formatters Angular Formatters}
|
||||
* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters}
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Data Binding in Angular
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Filters: Creating Angular Filters
|
||||
@description
|
||||
@@ -35,20 +34,26 @@ text upper-case and assigns color.
|
||||
}
|
||||
return out;
|
||||
});
|
||||
|
||||
function Ctrl() {
|
||||
this.greeting = 'hello';
|
||||
}
|
||||
</script>
|
||||
|
||||
<input name="text" type="text" value="hello" /><br>
|
||||
No filter: {{text}}<br>
|
||||
Reverse: {{text|reverse}}<br>
|
||||
Reverse + uppercase: {{text|reverse:true}}<br>
|
||||
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
|
||||
<div ng:controller="Ctrl">
|
||||
<input ng:model="greeting" type="greeting"><br>
|
||||
No filter: {{greeting}}<br>
|
||||
Reverse: {{greeting|reverse}}<br>
|
||||
Reverse + uppercase: {{greeting|reverse:true}}<br>
|
||||
Reverse + uppercase + blue: {{greeting|reverse:true:"blue"}}
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should reverse text', function(){
|
||||
expect(binding('text|reverse')).toEqual('olleh');
|
||||
input('text').enter('ABC');
|
||||
expect(binding('text|reverse')).toEqual('CBA');
|
||||
});
|
||||
it('should reverse greeting', function() {
|
||||
expect(binding('greeting|reverse')).toEqual('olleh');
|
||||
input('greeting').enter('ABC');
|
||||
expect(binding('greeting|reverse')).toEqual('CBA');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Understanding Angular Filters
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Filters: Using Angular Filters
|
||||
@description
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Angular Formatters: Creating Angular Formatters
|
||||
@description
|
||||
|
||||
To create your own formatter, you can simply register a pair of JavaScript functions with
|
||||
`angular.formatter`. One of your functions is used to parse text from the input widget into the
|
||||
data storage format; the other function is used to format stored data into user-readable text.
|
||||
|
||||
The following example demonstrates a "reverse" formatter. Data is stored in uppercase and in
|
||||
reverse, but it is displayed in lower case and non-reversed. When a user edits the data model via
|
||||
the input widget, the input is automatically parsed into the internal data storage format, and when
|
||||
the data changes in the model, it is automatically formatted to the user-readable form for display
|
||||
in the view.
|
||||
|
||||
<pre>
|
||||
function reverse(text) {
|
||||
var reversed = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
reversed.unshift(text.charAt(i));
|
||||
}
|
||||
return reversed.join('');
|
||||
}
|
||||
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
function reverse(text) {
|
||||
var reversed = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
reversed.unshift(text.charAt(i));
|
||||
}
|
||||
return reversed.join('');
|
||||
}
|
||||
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Angular Formatters
|
||||
@description
|
||||
|
||||
In angular, formatters are responsible for translating user-readable text entered in an {@link
|
||||
api/angular.widget.HTML input widget} to a JavaScript object in the data model that the application
|
||||
can manipulate.
|
||||
|
||||
You can use formatters in a template, and also in JavaScript. Angular provides built-in
|
||||
formatters, and of course you can create your own formatters.
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates.formatters.using_formatters Using Angular Formatters}
|
||||
* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.formatter Angular Formatter API}
|
||||
@@ -1,9 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Angular Formatters: Using Angular Formatters
|
||||
@description
|
||||
|
||||
The following snippet shows how to use a formatter in a template. The formatter below is
|
||||
`ng:format="reverse"`, added as an attribute to an `<input>` tag.
|
||||
|
||||
<pre>
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Understanding Angular Templates
|
||||
@description
|
||||
@@ -18,9 +17,7 @@ is {@link api/angular.widget.@ng:repeat ng:repeat}.
|
||||
* {@link dev_guide.compiler.markup Markup} — Shorthand for a widget or a directive. The double
|
||||
curly brace notation `{{ }}` to bind expressions to elements is built-in angular markup.
|
||||
* {@link dev_guide.templates.filters Filter} — Formats your data for display to the user.
|
||||
* {@link dev_guide.templates.validators Validator} — Lets you validate user input.
|
||||
* {@link dev_guide.templates.formatters Formatter} — Lets you format the input object into a user
|
||||
readable view.
|
||||
* {@link dev_guide.forms Form widgets} — Lets you validate user input.
|
||||
|
||||
Note: In addition to declaring the elements above in templates, you can also access these elements
|
||||
in JavaScript code.
|
||||
@@ -33,7 +30,7 @@ and {@link dev_guide.expressions expressions}:
|
||||
<html>
|
||||
<!-- Body tag augmented with ng:controller directive -->
|
||||
<body ng:controller="MyController">
|
||||
<input name="foo" value="bar">
|
||||
<input ng:model="foo" value="bar">
|
||||
<!-- Button tag with ng:click directive, and
|
||||
string expression 'buttonText'
|
||||
wrapped in "{{ }}" markup -->
|
||||
@@ -55,8 +52,7 @@ eight.
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates.filters Angular Filters}
|
||||
* {@link dev_guide.templates.formatters Angular Formatters}
|
||||
* {@link dev_guide.templates.validators Angular Validators}
|
||||
* {@link dev_guide.forms Angular Forms}
|
||||
|
||||
## Related API
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Validators: Creating Angular Validators
|
||||
@description
|
||||
|
||||
|
||||
To create a custom validator, you simply add your validator code as a method onto the
|
||||
`angular.validator` object and provide input(s) for the validator function. Each input provided is
|
||||
treated as an argument to the validator function. Any additional inputs should be separated by
|
||||
commas.
|
||||
|
||||
The following bit of pseudo-code shows how to set up a custom validator:
|
||||
|
||||
<pre>
|
||||
angular.validator('your_validator', function(input [,additional params]) {
|
||||
[your validation code];
|
||||
if ( [validation succeeds] ) {
|
||||
return false;
|
||||
} else {
|
||||
return true; // No error message specified
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
Note that this validator returns "true" when the user's input is incorrect, as in "Yes, it's true,
|
||||
there was a problem with that input". If you prefer to provide more information when a validator
|
||||
detects a problem with input, you can specify an error message in the validator that angular will
|
||||
display when the user hovers over the input widget.
|
||||
|
||||
To specify an error message, replace "`return true;`" with an error string, for example:
|
||||
|
||||
return "Must be a value between 1 and 5!";
|
||||
|
||||
Following is a sample UPS Tracking Number validator:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.validator('upsTrackingNo', function(input, format) {
|
||||
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
|
||||
return input.match(regexp)?"":"The format must match " + format;
|
||||
});
|
||||
</script>
|
||||
<input type="text" name="trackNo" size="40"
|
||||
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
|
||||
value="1Z 123 456 78 9012 345 6"/>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should validate correct UPS tracking number', function() {
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
|
||||
it('should not validate in correct UPS tracking number', function() {
|
||||
input('trackNo').enter('foo');
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
In this sample validator, we specify a regular expression against which to test the user's input.
|
||||
Note that when the user's input matches `regexp`, the function returns "false" (""); otherwise it
|
||||
returns the specified error message ("true").
|
||||
|
||||
Note: you can also access the current angular scope and DOM element objects in your validator
|
||||
functions as follows:
|
||||
|
||||
* `this` === The current angular scope.
|
||||
* `this.$element` === The DOM element that contains the binding. This allows the filter to
|
||||
manipulate the DOM in addition to transforming the input.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates Angular Templates}
|
||||
* {@link dev_guide.templates.filters Angular Filters}
|
||||
* {@link dev_guide.templates.formatters Angular Formatters}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.validator API Validator Reference}
|
||||
@@ -1,131 +0,0 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Templates: Understanding Angular Validators
|
||||
@description
|
||||
|
||||
Angular validators are attributes that test the validity of different types of user input. Angular
|
||||
provides a set of built-in input validators:
|
||||
|
||||
* {@link api/angular.validator.phone phone number}
|
||||
* {@link api/angular.validator.number number}
|
||||
* {@link api/angular.validator.integer integer}
|
||||
* {@link api/angular.validator.date date}
|
||||
* {@link api/angular.validator.email email address}
|
||||
* {@link api/angular.validator.json JSON}
|
||||
* {@link api/angular.validator.regexp regular expressions}
|
||||
* {@link api/angular.validator.url URLs}
|
||||
* {@link api/angular.validator.asynchronous asynchronous}
|
||||
|
||||
You can also create your own custom validators.
|
||||
|
||||
# Using Angular Validators
|
||||
|
||||
You can use angular validators in HTML template bindings, and in JavaScript:
|
||||
|
||||
* Validators in HTML Template Bindings
|
||||
|
||||
<pre>
|
||||
<input ng:validator="validator_type:parameters" [...]>
|
||||
</pre>
|
||||
|
||||
* Validators in JavaScript
|
||||
|
||||
<pre>
|
||||
angular.validator.[validator_type](parameters)
|
||||
</pre>
|
||||
|
||||
The following example shows how to use the built-in angular integer validator:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Change me: <input type="text" name="number" ng:validate="integer" value="123">
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should validate the default number string', function() {
|
||||
expect(element('input[name=number]').attr('class')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
it('should not validate "foo"', function() {
|
||||
input('number').enter('foo');
|
||||
expect(element('input[name=number]').attr('class')).
|
||||
toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Creating an Angular Validator
|
||||
|
||||
To create a custom validator, you simply add your validator code as a method onto the
|
||||
`angular.validator` object and provide input(s) for the validator function. Each input provided is
|
||||
treated as an argument to the validator function. Any additional inputs should be separated by
|
||||
commas.
|
||||
|
||||
The following bit of pseudo-code shows how to set up a custom validator:
|
||||
|
||||
<pre>
|
||||
angular.validator('your_validator', function(input [,additional params]) {
|
||||
[your validation code];
|
||||
if ( [validation succeeds] ) {
|
||||
return false;
|
||||
} else {
|
||||
return true; // No error message specified
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
Note that this validator returns "true" when the user's input is incorrect, as in "Yes, it's true,
|
||||
there was a problem with that input". If you prefer to provide more information when a validator
|
||||
detects a problem with input, you can specify an error message in the validator that angular will
|
||||
display when the user hovers over the input widget.
|
||||
|
||||
To specify an error message, replace "`return true;`" with an error string, for example:
|
||||
|
||||
return "Must be a value between 1 and 5!";
|
||||
|
||||
Following is a sample UPS Tracking Number validator:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.validator('upsTrackingNo', function(input, format) {
|
||||
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
|
||||
return input.match(regexp)?"":"The format must match " + format;
|
||||
});
|
||||
</script>
|
||||
<input type="text" name="trackNo" size="40"
|
||||
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
|
||||
value="1Z 123 456 78 9012 345 6"/>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should validate correct UPS tracking number', function() {
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
not().toMatch(/ng-validation-error/);
|
||||
});
|
||||
|
||||
it('should not validate in correct UPS tracking number', function() {
|
||||
input('trackNo').enter('foo');
|
||||
expect(element('input[name=trackNo]').attr('class')).
|
||||
toMatch(/ng-validation-error/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
In this sample validator, we specify a regular expression against which to test the user's input.
|
||||
Note that when the user's input matches `regexp`, the function returns "false" (""); otherwise it
|
||||
returns the specified error message ("true").
|
||||
|
||||
Note: you can also access the current angular scope and DOM element objects in your validator
|
||||
functions as follows:
|
||||
|
||||
* `this` === The current angular scope.
|
||||
* `this.$element` === The DOM element that contains the binding. This allows the filter to
|
||||
manipulate the DOM in addition to transforming the input.
|
||||
|
||||
|
||||
## Related Topics
|
||||
|
||||
* {@link dev_guide.templates Angular Templates}
|
||||
|
||||
## Related API
|
||||
|
||||
* {@link api/angular.validator Validator API}
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Unit Testing
|
||||
@description
|
||||
@@ -43,11 +42,11 @@ on a constructor permanently binds the call site to the type. For example lets s
|
||||
trying to instantiate an `XHR` so that we can get some data from the server.
|
||||
|
||||
<pre>
|
||||
function MyClass(){
|
||||
this.doWork = function(){
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
var xhr = new XHR();
|
||||
xhr.open(method, url, true);
|
||||
xhr.onreadystatechange = function(){...}
|
||||
xhr.onreadystatechange = function() {...}
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
@@ -61,7 +60,7 @@ patching, that is a bad idea for many reasons, which is outside the scope of thi
|
||||
The class above is hard to test since we have to resort to monkey patching:
|
||||
<pre>
|
||||
var oldXHR = XHR;
|
||||
XHR = function MockXHR(){};
|
||||
XHR = function MockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that MockXHR got called with the right arguments
|
||||
@@ -73,8 +72,8 @@ XHR = oldXHR; // if you forget this bad things will happen
|
||||
Another way to approach the problem is look for the service in a well known location.
|
||||
|
||||
<pre>
|
||||
function MyClass(){
|
||||
this.doWork = function(){
|
||||
function MyClass() {
|
||||
this.doWork = function() {
|
||||
global.xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
@@ -94,7 +93,7 @@ State & Singletons}
|
||||
The class above is hard to test since we have to change global state:
|
||||
<pre>
|
||||
var oldXHR = glabal.xhr;
|
||||
glabal.xhr = function mockXHR(){};
|
||||
glabal.xhr = function mockXHR() {};
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
@@ -110,7 +109,7 @@ having the tests replace the services as needed.
|
||||
<pre>
|
||||
function MyClass() {
|
||||
var serviceRegistry = ????;
|
||||
this.doWork = function(){
|
||||
this.doWork = function() {
|
||||
var xhr = serviceRegistry.get('xhr');
|
||||
xhr({
|
||||
method:'...',
|
||||
@@ -128,7 +127,7 @@ there is only one global variable to be reset).
|
||||
The class above is hard to test since we have to change global state:
|
||||
<pre>
|
||||
var oldServiceLocator = glabal.serviceLocator;
|
||||
glabal.serviceLocator.set('xhr', function mockXHR(){});
|
||||
glabal.serviceLocator.set('xhr', function mockXHR() {});
|
||||
var myClass = new MyClass();
|
||||
myClass.doWork();
|
||||
// assert that mockXHR got called with the right arguments
|
||||
@@ -141,7 +140,7 @@ Lastly the dependency can be passed in.
|
||||
|
||||
<pre>
|
||||
function MyClass(xhr) {
|
||||
this.doWork = function(){
|
||||
this.doWork = function() {
|
||||
xhr({
|
||||
method:'...',
|
||||
url:'...',
|
||||
@@ -174,13 +173,13 @@ for your application is mixed in with DOM manipulation, it will be hard to test
|
||||
below:
|
||||
|
||||
<pre>
|
||||
function PasswordController(){
|
||||
function PasswordController() {
|
||||
// get references to DOM elements
|
||||
var msg = $('.ex1 span');
|
||||
var input = $('.ex1 input');
|
||||
var strength;
|
||||
|
||||
this.grade = function(){
|
||||
this.grade = function() {
|
||||
msg.removeClass(strength);
|
||||
var pwd = input.val();
|
||||
password.text(pwd);
|
||||
@@ -219,9 +218,9 @@ In angular the controllers are strictly separated from the DOM manipulation logi
|
||||
a much easier testability story as can be seen in this example:
|
||||
|
||||
<pre>
|
||||
function PasswordCntrl(){
|
||||
function PasswordCntrl() {
|
||||
this.password = '';
|
||||
this.grade = function(){
|
||||
this.grade = function() {
|
||||
var size = this.password.length;
|
||||
if (size > 8) {
|
||||
this.strength = 'strong';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide
|
||||
@description
|
||||
@@ -42,8 +41,7 @@ of the following documents before returning here to the Developer Guide:
|
||||
## {@link dev_guide.templates Angular Templates}
|
||||
|
||||
* {@link dev_guide.templates.filters Understanding Angular Filters}
|
||||
* {@link dev_guide.templates.formatters Understanding Angular Formatters}
|
||||
* {@link dev_guide.templates.validators Understanding Angular Validators}
|
||||
* {@link dev_guide.forms Understanding Angular Forms}
|
||||
|
||||
## {@link dev_guide.services Angular Services}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Downloading
|
||||
@description
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name FAQ
|
||||
@description
|
||||
|
||||
@@ -67,7 +67,7 @@ This example demonstrates angular's two-way data binding:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" name="yourname" value="World"/>
|
||||
Your name: <input type="text" ng:model="yourname" value="World"/>
|
||||
<hr/>
|
||||
Hello {{yourname}}!
|
||||
</doc:source>
|
||||
@@ -136,7 +136,7 @@ In addition, angular comes with a set of Services, which have the following prop
|
||||
# Where To Go Next
|
||||
|
||||
* If you like what you've learned so far, you should definitely check out our awesome {@link
|
||||
tutorial/ Tutorial}, which walk you through the process of building real apps with AngularJS.
|
||||
tutorial/ Tutorial}, which walks you through the process of building real apps with AngularJS.
|
||||
|
||||
* For further explanations and examples of the AngularJS concepts presented on this page, see the
|
||||
{@link guide/index Developer Guide}.
|
||||
|
||||
@@ -69,7 +69,7 @@ view.
|
||||
## Model and Controller
|
||||
|
||||
The data __model__ (a simple array of phones in object literal notation) is instantiated within
|
||||
the __controller__ function (`PhoneListCtrl`):
|
||||
the __controller__ function(`PhoneListCtrl`):
|
||||
|
||||
__`app/js/controllers.js`:__
|
||||
<pre>
|
||||
@@ -91,7 +91,7 @@ providing context for our data model, the controller allows us to establish data
|
||||
the model and the view. We connected the dots between the presentation, data, and logic components
|
||||
as follows:
|
||||
|
||||
* The name of our controller function (in the JavaScript file `controllers.js`) matches the {@link
|
||||
* The name of our controller function(in the JavaScript file `controllers.js`) matches the {@link
|
||||
api/angular.directive.ng:controller ng:controller} directive in the `<body>` tag (`PhoneListCtrl`).
|
||||
* The data is instantiated within the *scope* of our controller function; our template binding
|
||||
points are located within the block bounded by the `<body ng:controller="PhoneListCtrl">` tag.
|
||||
|
||||
@@ -32,7 +32,7 @@ We made no changes to the controller.
|
||||
__`app/index.html`:__
|
||||
<pre>
|
||||
...
|
||||
Fulltext Search: <input name="query"/>
|
||||
Fulltext Search: <input ng:model="query"/>
|
||||
|
||||
<ul class="phones">
|
||||
<li ng:repeat="phone in phones.$filter(query)">
|
||||
|
||||
@@ -27,11 +27,11 @@ __`app/index.html`:__
|
||||
...
|
||||
<ul class="controls">
|
||||
<li>
|
||||
Search: <input type="text" name="query"/>
|
||||
Search: <input type="text" ng:model="query"/>
|
||||
</li>
|
||||
<li>
|
||||
Sort by:
|
||||
<select name="orderProp">
|
||||
<select ng:model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
@@ -114,7 +114,7 @@ __`test/unit/controllerSpec.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
describe('PhoneListCtrl', function(){
|
||||
describe('PhoneListCtrl', function() {
|
||||
var scope, $browser, ctrl;
|
||||
|
||||
beforeEach(function() {
|
||||
|
||||
@@ -122,11 +122,11 @@ __`app/partials/phone-list.html`:__
|
||||
<pre>
|
||||
<ul class="predicates">
|
||||
<li>
|
||||
Search: <input type="text" name="query"/>
|
||||
Search: <input type="text" ng:model="query"/>
|
||||
</li>
|
||||
<li>
|
||||
Sort by:
|
||||
<select name="orderProp">
|
||||
<select ng:model="orderProp">
|
||||
<option value="name">Alphabetical</option>
|
||||
<option value="age">Newest</option>
|
||||
</select>
|
||||
|
||||
@@ -109,7 +109,7 @@ following bindings to `index.html`:
|
||||
* We can also create a model with an input element, and combine it with a filtered binding. Add
|
||||
the following to index.html:
|
||||
|
||||
<input name="userInput"> Uppercased: {{ userInput | uppercase }}
|
||||
<input ng:model="userInput"> Uppercased: {{ userInput | uppercase }}
|
||||
|
||||
|
||||
# Summary
|
||||
|
||||
@@ -129,7 +129,7 @@ __`test/unit/controllersSpec.js`:__
|
||||
<pre>
|
||||
describe('PhoneCat controllers', function() {
|
||||
|
||||
beforeEach(function(){
|
||||
beforeEach(function() {
|
||||
this.addMatchers({
|
||||
toEqualData: function(expected) {
|
||||
return angular.equals(this.actual, expected);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<label>Name:</label>
|
||||
<input type="text" name="form.name" ng:required>
|
||||
<input type="text" ng:model="form.name" required>
|
||||
|
||||
<div ng:repeat="contact in form.contacts">
|
||||
<select name="contact.type">
|
||||
<select ng:model="contact.type">
|
||||
<option>url</option>
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
</select>
|
||||
<input type="text" name="contact.url">
|
||||
<input type="text" ng:model="contact.url">
|
||||
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<div>
|
||||
@@ -15,4 +15,4 @@
|
||||
</div>
|
||||
|
||||
<button ng:click="cancel()">Cancel</button>
|
||||
<button ng:click="save()">Save</button>
|
||||
<button ng:click="save()">Save</button>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -1,15 +1,15 @@
|
||||
var DOM = require('dom.js').DOM;
|
||||
|
||||
describe('dom', function(){
|
||||
describe('dom', function() {
|
||||
var dom;
|
||||
|
||||
beforeEach(function(){
|
||||
beforeEach(function() {
|
||||
dom = new DOM();
|
||||
});
|
||||
|
||||
describe('h', function(){
|
||||
describe('h', function() {
|
||||
|
||||
it('should render using function', function(){
|
||||
it('should render using function', function() {
|
||||
var cbThis;
|
||||
var cdValue;
|
||||
dom.h('heading', 'content', function(value){
|
||||
@@ -20,8 +20,8 @@ describe('dom', function(){
|
||||
expect(cbValue).toEqual('content');
|
||||
});
|
||||
|
||||
it('should update heading numbers', function(){
|
||||
dom.h('heading', function(){
|
||||
it('should update heading numbers', function() {
|
||||
dom.h('heading', function() {
|
||||
this.html('<h1>sub-heading</h1>');
|
||||
});
|
||||
expect(dom.toString()).toContain('<h1>heading</h1>');
|
||||
|
||||
+55
-87
@@ -1,11 +1,11 @@
|
||||
var ngdoc = require('ngdoc.js');
|
||||
var DOM = require('dom.js').DOM;
|
||||
|
||||
describe('ngdoc', function(){
|
||||
describe('ngdoc', function() {
|
||||
var Doc = ngdoc.Doc;
|
||||
var dom;
|
||||
|
||||
beforeEach(function(){
|
||||
beforeEach(function() {
|
||||
dom = new DOM();
|
||||
this.addMatchers({
|
||||
toContain: function(text) {
|
||||
@@ -15,15 +15,15 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('Doc', function(){
|
||||
describe('metadata', function(){
|
||||
describe('Doc', function() {
|
||||
describe('metadata', function() {
|
||||
|
||||
it('should find keywords', function(){
|
||||
it('should find keywords', function() {
|
||||
expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world');
|
||||
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
|
||||
});
|
||||
|
||||
it('should have shortName', function(){
|
||||
it('should have shortName', function() {
|
||||
var d1 = new Doc('@name a.b.c').parse();
|
||||
var d2 = new Doc('@name a.b.ng:c').parse();
|
||||
var d3 = new Doc('@name some text: more text').parse();
|
||||
@@ -32,7 +32,7 @@ describe('ngdoc', function(){
|
||||
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
|
||||
});
|
||||
|
||||
it('should have depth information', function(){
|
||||
it('should have depth information', function() {
|
||||
var d1 = new Doc('@name a.b.c').parse();
|
||||
var d2 = new Doc('@name a.b.ng:c').parse();
|
||||
var d3 = new Doc('@name some text: more text').parse();
|
||||
@@ -43,8 +43,8 @@ describe('ngdoc', function(){
|
||||
|
||||
});
|
||||
|
||||
describe('parse', function(){
|
||||
it('should convert @names into properties', function(){
|
||||
describe('parse', function() {
|
||||
it('should convert @names into properties', function() {
|
||||
var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n');
|
||||
doc.parse();
|
||||
expect(doc.name).toEqual('name');
|
||||
@@ -52,7 +52,7 @@ describe('ngdoc', function(){
|
||||
expect(doc.dep).toEqual('');
|
||||
});
|
||||
|
||||
it('should parse parameters', function(){
|
||||
it('should parse parameters', function() {
|
||||
var doc = new Doc(
|
||||
'@param {*} a short\n' +
|
||||
'@param {Type} b med\n' +
|
||||
@@ -65,7 +65,7 @@ describe('ngdoc', function(){
|
||||
]);
|
||||
});
|
||||
|
||||
it('should parse return', function(){
|
||||
it('should parse return', function() {
|
||||
var doc = new Doc('@returns {Type} text *bold*.');
|
||||
doc.parse();
|
||||
expect(doc.returns).toEqual({
|
||||
@@ -74,14 +74,14 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse filename', function(){
|
||||
it('should parse filename', function() {
|
||||
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
|
||||
doc.parse(0);
|
||||
expect(doc.id).toEqual('a.b');
|
||||
expect(doc.name).toEqual('friendly name');
|
||||
});
|
||||
|
||||
it('should escape <doc:source> element', function(){
|
||||
it('should escape <doc:source> element', function() {
|
||||
var doc = new Doc('@description before <doc:example>' +
|
||||
'<doc:source>\n<>\n</doc:source></doc:example> after');
|
||||
doc.parse();
|
||||
@@ -89,7 +89,7 @@ describe('ngdoc', function(){
|
||||
'<pre class="doc-source">\n<>\n</pre></doc:example><p>after</p>');
|
||||
});
|
||||
|
||||
it('should preserve the jsfiddle attribute', function(){
|
||||
it('should preserve the jsfiddle attribute', function() {
|
||||
var doc = new Doc('@description before <doc:example>' +
|
||||
'<doc:source jsfiddle="foo">lala</doc:source></doc:example> after');
|
||||
doc.parse();
|
||||
@@ -97,7 +97,7 @@ describe('ngdoc', function(){
|
||||
'<pre class="doc-source" jsfiddle="foo">lala</pre></doc:example><p>after</p>');
|
||||
});
|
||||
|
||||
it('should escape <doc:scenario> element', function(){
|
||||
it('should escape <doc:scenario> element', function() {
|
||||
var doc = new Doc('@description before <doc:example>' +
|
||||
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
|
||||
doc.parse();
|
||||
@@ -132,11 +132,11 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('sorting', function(){
|
||||
describe('sorting', function() {
|
||||
function property(name) {
|
||||
return function(obj) {return obj[name];};
|
||||
}
|
||||
function noop(){}
|
||||
function noop() {}
|
||||
function doc(type, name){
|
||||
return {
|
||||
id: name,
|
||||
@@ -149,7 +149,7 @@ describe('ngdoc', function(){
|
||||
var angular_x = doc('function', 'angular.x');
|
||||
var angular_y = doc('property', 'angular.y');
|
||||
|
||||
it('should put angular.fn() in front of angular.widget, etc', function(){
|
||||
it('should put angular.fn() in front of angular.widget, etc', function() {
|
||||
expect(ngdoc.metadata([angular_widget, angular_y, angular_x]).map(property('id')))
|
||||
.toEqual(['angular.x', 'angular.y', 'angular.widget' ]);
|
||||
});
|
||||
@@ -157,13 +157,13 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdown', function(){
|
||||
it('should replace angular in markdown', function(){
|
||||
describe('markdown', function() {
|
||||
it('should replace angular in markdown', function() {
|
||||
expect(new Doc().markdown('<angular/>')).
|
||||
toEqual('<p><tt><angular/></tt></p>');
|
||||
});
|
||||
|
||||
it('should not replace anything in <pre>, but escape the html escape the content', function(){
|
||||
it('should not replace anything in <pre>, but escape the html escape the content', function() {
|
||||
expect(new Doc().markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
|
||||
toEqual(
|
||||
'<p>bah x</p>' +
|
||||
@@ -194,12 +194,12 @@ describe('ngdoc', function(){
|
||||
it('should ignore nested doc widgets', function() {
|
||||
expect(new Doc().markdown(
|
||||
'before<doc:tutorial-instructions>\n' +
|
||||
'<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">' +
|
||||
'<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">' +
|
||||
'\ngit bla bla\n</doc:tutorial-instruction>\n' +
|
||||
'</doc:tutorial-instructions>')).toEqual(
|
||||
|
||||
'<p>before</p><doc:tutorial-instructions>\n' +
|
||||
'<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">\n' +
|
||||
'<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">\n' +
|
||||
'git bla bla\n' +
|
||||
'</doc:tutorial-instruction>\n' +
|
||||
'</doc:tutorial-instructions>');
|
||||
@@ -234,19 +234,19 @@ describe('ngdoc', function(){
|
||||
|
||||
});
|
||||
|
||||
describe('trim', function(){
|
||||
describe('trim', function() {
|
||||
var trim = ngdoc.trim;
|
||||
it('should remove leading/trailing space', function(){
|
||||
it('should remove leading/trailing space', function() {
|
||||
expect(trim(' \nabc\n ')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should remove leading space on every line', function(){
|
||||
it('should remove leading space on every line', function() {
|
||||
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('merge', function(){
|
||||
it('should merge child with parent', function(){
|
||||
describe('merge', function() {
|
||||
it('should merge child with parent', function() {
|
||||
var parent = new Doc({id: 'angular.service.abc', name: 'angular.service.abc', section: 'api'});
|
||||
var methodA = new Doc({name: 'methodA', methodOf: 'angular.service.abc'});
|
||||
var methodB = new Doc({name: 'methodB', methodOf: 'angular.service.abc'});
|
||||
@@ -294,9 +294,9 @@ describe('ngdoc', function(){
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
describe('TAG', function(){
|
||||
describe('@param', function(){
|
||||
it('should parse with no default', function(){
|
||||
describe('TAG', function() {
|
||||
describe('@param', function() {
|
||||
it('should parse with no default', function() {
|
||||
var doc = new Doc('@param {(number|string)} number Number \n to format.');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([{
|
||||
@@ -307,7 +307,7 @@ describe('ngdoc', function(){
|
||||
description : '<p>Number \nto format.</p>' }]);
|
||||
});
|
||||
|
||||
it('should parse with default and optional', function(){
|
||||
it('should parse with default and optional', function() {
|
||||
var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc');
|
||||
doc.parse();
|
||||
expect(doc.param).toEqual([{
|
||||
@@ -327,8 +327,8 @@ describe('ngdoc', function(){
|
||||
expect(doc.requires).toEqual([
|
||||
{name:'$service', text:'<p>for \n<code>A</code></p>'},
|
||||
{name:'$another', text:'<p>for <code>B</code></p>'}]);
|
||||
expect(doc.html()).toContain('<a href="#!/api/angular.service.$service">$service</a>');
|
||||
expect(doc.html()).toContain('<a href="#!/api/angular.service.$another">$another</a>');
|
||||
expect(doc.html()).toContain('<a href="api/angular.service.$service">$service</a>');
|
||||
expect(doc.html()).toContain('<a href="api/angular.service.$another">$another</a>');
|
||||
expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
|
||||
expect(doc.html()).toContain('<p>for <code>B</code></p>');
|
||||
});
|
||||
@@ -397,8 +397,8 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('@description', function(){
|
||||
it('should support pre blocks', function(){
|
||||
describe('@description', function() {
|
||||
it('should support pre blocks', function() {
|
||||
var doc = new Doc("@description <pre><b>abc</b></pre>");
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
@@ -429,37 +429,37 @@ describe('ngdoc', function(){
|
||||
doc.parse();
|
||||
|
||||
expect(doc.description).
|
||||
toContain('foo <a href="#!/api/angular.foo"><code>angular.foo</code></a>');
|
||||
toContain('foo <a href="api/angular.foo"><code>angular.foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('da <a href="#!/api/angular.foo"><code>bar foo bar</code></a>');
|
||||
toContain('da <a href="api/angular.foo"><code>bar foo bar</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('dad<a href="#!/api/angular.foo"><code>angular.foo</code></a>');
|
||||
toContain('dad<a href="api/angular.foo"><code>angular.foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!/api/angular.directive.ng:foo"><code>ng:foo</code></a>');
|
||||
toContain('<a href="api/angular.directive.ng:foo"><code>ng:foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="./static.html">./static.html</a>');
|
||||
});
|
||||
|
||||
it('should support line breaks in @link', function(){
|
||||
it('should support line breaks in @link', function() {
|
||||
var doc = new Doc("@description " +
|
||||
'{@link\napi/angular.foo\na\nb}');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!/api/angular.foo"><code>a b</code></a>');
|
||||
toContain('<a href="api/angular.foo"><code>a b</code></a>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('@example', function(){
|
||||
it('should not remove {{}}', function(){
|
||||
describe('@example', function() {
|
||||
it('should not remove {{}}', function() {
|
||||
var doc = new Doc('@example text {{ abc }}');
|
||||
doc.parse();
|
||||
expect(doc.example).toEqual('<p>text {{ abc }}</p>');
|
||||
});
|
||||
|
||||
it('should support doc:example', function(){
|
||||
it('should support doc:example', function() {
|
||||
var doc = new Doc('@ngdoc overview\n@example \n' +
|
||||
'<doc:example>\n' +
|
||||
' <doc:source><escapeme></doc:source>\n' +
|
||||
@@ -480,7 +480,7 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('@this', function(){
|
||||
describe('@this', function() {
|
||||
it('should render @this', function() {
|
||||
var doc = new Doc('@this I am self.');
|
||||
doc.ngdoc = 'filter';
|
||||
@@ -495,9 +495,9 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('usage', function(){
|
||||
describe('overview', function(){
|
||||
it('should supress description heading', function(){
|
||||
describe('usage', function() {
|
||||
describe('overview', function() {
|
||||
it('should supress description heading', function() {
|
||||
var doc = new Doc('@ngdoc overview\n@name angular\n@description\n#heading\ntext');
|
||||
doc.parse();
|
||||
expect(doc.html()).toContain('text');
|
||||
@@ -507,8 +507,8 @@ describe('ngdoc', function(){
|
||||
});
|
||||
|
||||
|
||||
describe('function', function(){
|
||||
it('should format', function(){
|
||||
describe('function', function() {
|
||||
it('should format', function() {
|
||||
var doc = new Doc({
|
||||
ngdoc:'function',
|
||||
name:'some.name',
|
||||
@@ -527,8 +527,8 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('filter', function(){
|
||||
it('should format', function(){
|
||||
describe('filter', function() {
|
||||
it('should format', function() {
|
||||
var doc = new Doc({
|
||||
ngdoc:'formatter',
|
||||
shortName:'myFilter',
|
||||
@@ -543,40 +543,8 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('validator', function(){
|
||||
it('should format', function(){
|
||||
var doc = new Doc({
|
||||
ngdoc:'validator',
|
||||
shortName:'myValidator',
|
||||
param: [
|
||||
{name:'a'},
|
||||
{name:'b'}
|
||||
]
|
||||
});
|
||||
doc.html_usage_validator(dom);
|
||||
expect(dom).toContain('ng:validate="myValidator:b"');
|
||||
expect(dom).toContain('angular.validator.myValidator(a, b)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatter', function(){
|
||||
it('should format', function(){
|
||||
var doc = new Doc({
|
||||
ngdoc:'formatter',
|
||||
shortName:'myFormatter',
|
||||
param: [
|
||||
{name:'a'},
|
||||
]
|
||||
});
|
||||
doc.html_usage_formatter(dom);
|
||||
expect(dom).toContain('ng:format="myFormatter:a"');
|
||||
expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);');
|
||||
expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);');
|
||||
});
|
||||
});
|
||||
|
||||
describe('property', function(){
|
||||
it('should format', function(){
|
||||
describe('property', function() {
|
||||
it('should format', function() {
|
||||
var doc = new Doc({
|
||||
ngdoc:'property',
|
||||
name:'myProp',
|
||||
|
||||
@@ -2,8 +2,8 @@ var SiteMap = require('SiteMap.js').SiteMap;
|
||||
var Doc = require('ngdoc.js').Doc;
|
||||
|
||||
|
||||
describe('sitemap', function(){
|
||||
it('should render empty sitemap', function(){
|
||||
describe('sitemap', function() {
|
||||
it('should render empty sitemap', function() {
|
||||
var map = new SiteMap([]);
|
||||
expect(map.render()).toEqual([
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
@@ -11,7 +11,7 @@ describe('sitemap', function(){
|
||||
'</urlset>', ''].join('\n'));
|
||||
});
|
||||
|
||||
it('should render ngdoc url', function(){
|
||||
it('should render ngdoc url', function() {
|
||||
var map = new SiteMap([new Doc({section: 'foo', id: 'a.b.c<>\'"&'})]);
|
||||
expect(map.render()).toContain([
|
||||
' <url>',
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ for(var key in jasmine) {
|
||||
}
|
||||
|
||||
//Patch Jasmine for proper stack traces
|
||||
jasmine.Spec.prototype.fail = function (e) {
|
||||
jasmine.Spec.prototype.fail = function(e) {
|
||||
var expectationResult = new jasmine.ExpectationResult({
|
||||
passed: false,
|
||||
message: e ? jasmine.util.formatException(e) : 'Exception'
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
var writer = require('writer.js');
|
||||
describe('writer', function(){
|
||||
describe('toString', function(){
|
||||
describe('writer', function() {
|
||||
describe('toString', function() {
|
||||
var toString = writer.toString;
|
||||
|
||||
it('should merge string', function(){
|
||||
it('should merge string', function() {
|
||||
expect(toString('abc')).toEqual('abc');
|
||||
});
|
||||
|
||||
it('should merge obj', function(){
|
||||
it('should merge obj', function() {
|
||||
expect(toString({a:1})).toEqual('{"a":1}');
|
||||
});
|
||||
|
||||
it('should merge array', function(){
|
||||
it('should merge array', function() {
|
||||
expect(toString(['abc',{}])).toEqual('abc{}');
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ exports.SiteMap = SiteMap;
|
||||
* @returns {SiteMap}
|
||||
*/
|
||||
function SiteMap(docs){
|
||||
this.render = function(){
|
||||
this.render = function() {
|
||||
var map = [];
|
||||
map.push('<?xml version="1.0" encoding="UTF-8"?>');
|
||||
map.push('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
|
||||
|
||||
@@ -29,7 +29,7 @@ function appCache(path) {
|
||||
|
||||
var resultPostfix = ["",
|
||||
"FALLBACK:",
|
||||
"/offline.html",
|
||||
"/ /build/docs/index.html",
|
||||
"",
|
||||
"# allow access to google analytics and twitter when we are online",
|
||||
"NETWORK:",
|
||||
@@ -68,7 +68,7 @@ function appCacheTemplate() {
|
||||
"img/yellow_bkgnd.jpg",
|
||||
"",
|
||||
"FALLBACK:",
|
||||
"/ offline.html",
|
||||
"/ /build/docs/offline.html",
|
||||
"",
|
||||
"# allow access to google analytics and twitter when we are online",
|
||||
"NETWORK:",
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ function htmlEscape(text){
|
||||
}
|
||||
|
||||
|
||||
function DOM(){
|
||||
function DOM() {
|
||||
this.out = [];
|
||||
this.headingDepth = 0;
|
||||
}
|
||||
@@ -69,7 +69,7 @@ DOM.prototype = {
|
||||
},
|
||||
|
||||
code: function(text) {
|
||||
this.tag('div', {'ng:non-bindable':''}, function(){
|
||||
this.tag('div', {'ng:non-bindable':''}, function() {
|
||||
this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
|
||||
});
|
||||
},
|
||||
@@ -84,7 +84,7 @@ DOM.prototype = {
|
||||
if (content instanceof Array) {
|
||||
this.ul(content, className, fn);
|
||||
} else if (fn) {
|
||||
this.tag('div', className, function(){
|
||||
this.tag('div', className, function() {
|
||||
fn.call(this, content);
|
||||
});
|
||||
} else {
|
||||
|
||||
+18
-19
@@ -7,7 +7,7 @@ var reader = require('reader.js'),
|
||||
appCache = require('appCache.js').appCache,
|
||||
Q = require('qq');
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
process.on('uncaughtException', function(err) {
|
||||
console.error(err.stack || err);
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ writer.makeDir('build/docs/syntaxhighlighter').then(function() {
|
||||
ngdoc.merge(docs);
|
||||
var fileFutures = [];
|
||||
docs.forEach(function(doc){
|
||||
fileFutures.push(writer.output(doc.section + '/' + doc.id + '.html', doc.html()));
|
||||
fileFutures.push(writer.output('partials/' + doc.section + '/' + doc.id + '.html', doc.html()));
|
||||
});
|
||||
|
||||
writeTheRest(fileFutures);
|
||||
@@ -30,7 +30,7 @@ writer.makeDir('build/docs/syntaxhighlighter').then(function() {
|
||||
return Q.deep(fileFutures);
|
||||
}).then(function generateManifestFile() {
|
||||
return appCache('build/docs/').then(function(list) {
|
||||
writer.output('appcache-offline.manifest',list)
|
||||
writer.output('appcache-offline.manifest', list);
|
||||
});
|
||||
}).then(function printStats() {
|
||||
console.log('DONE. Generated ' + docs.length + ' pages in ' + (now()-start) + 'ms.' );
|
||||
@@ -43,28 +43,27 @@ function writeTheRest(writesFuture) {
|
||||
writesFuture.push(writer.copyDir('img'));
|
||||
writesFuture.push(writer.copyDir('examples'));
|
||||
|
||||
var manifest = 'manifest="appcache.manifest"',
|
||||
jq = '<script src="jquery.min.js"></script>',
|
||||
ngMin = '<script src="../angular.min.js" ng:autobind></script>',
|
||||
ng = '<script src="../angular.js" ng:autobind></script>'
|
||||
var manifest = 'manifest="/build/docs/appcache.manifest"';
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index.html',
|
||||
writer.replace, {'doc:manifest': manifest,
|
||||
'<!-- angular script place holder -->': ngMin}));
|
||||
writer.replace, {'doc:manifest': manifest}));
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-nocache.html',
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq.html',
|
||||
writer.replace, {'doc:manifest': manifest,
|
||||
'<!-- angular script place holder -->': ngMin,
|
||||
'<!-- jquery place holder -->': jq}));
|
||||
writer.replace, {'doc:manifest': manifest}));
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-nocache.html',
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-debug.html',
|
||||
writer.replace, {'doc:manifest': '',
|
||||
'<!-- angular script place holder -->': ng}));
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-jq-debug.html',
|
||||
writer.replace, {'doc:manifest': '',
|
||||
'<!-- angular script place holder -->': ng,
|
||||
'<!-- jquery place holder -->': jq}));
|
||||
writer.replace, {'doc:manifest': ''}));
|
||||
|
||||
writesFuture.push(writer.copyTpl('offline.html'));
|
||||
writesFuture.push(writer.copyTpl('docs-scenario.html'));
|
||||
@@ -94,6 +93,6 @@ function writeTheRest(writesFuture) {
|
||||
}
|
||||
|
||||
|
||||
function now(){ return new Date().getTime(); }
|
||||
function now() { return new Date().getTime(); }
|
||||
|
||||
function noop(){};
|
||||
function noop() {};
|
||||
|
||||
+74
-114
@@ -13,6 +13,11 @@ exports.scenarios = scenarios;
|
||||
exports.merge = merge;
|
||||
exports.Doc = Doc;
|
||||
|
||||
var BOOLEAN_ATTR = {};
|
||||
['multiple', 'selected', 'checked', 'disabled', 'readOnly', 'required'].forEach(function(value, key) {
|
||||
BOOLEAN_ATTR[value] = true;
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function Doc(text, file, line) {
|
||||
if (typeof text == 'object') {
|
||||
@@ -32,14 +37,14 @@ function Doc(text, file, line) {
|
||||
this.events = this.events || [];
|
||||
this.links = this.links || [];
|
||||
}
|
||||
Doc.METADATA_IGNORE = (function(){
|
||||
Doc.METADATA_IGNORE = (function() {
|
||||
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
|
||||
return words.toString().split(/[,\s\n\r]+/gm);
|
||||
})();
|
||||
|
||||
|
||||
Doc.prototype = {
|
||||
keywords: function keywords(){
|
||||
keywords: function keywords() {
|
||||
var keywords = {};
|
||||
Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
|
||||
var words = [];
|
||||
@@ -78,7 +83,7 @@ Doc.prototype = {
|
||||
return this.section + '/' + url;
|
||||
},
|
||||
|
||||
markdown: function (text) {
|
||||
markdown: function(text) {
|
||||
if (!text) return text;
|
||||
|
||||
var self = this,
|
||||
@@ -133,7 +138,7 @@ Doc.prototype = {
|
||||
|
||||
if (!isFullUrl) self.links.push(absUrl);
|
||||
|
||||
return '<a href="' + (isFullUrl ? '' + url : '#!/' + absUrl) + '">'
|
||||
return '<a href="' + absUrl + '">'
|
||||
+ (isAngular ? '<code>' : '')
|
||||
+ (title || url).replace(/\n/g, ' ')
|
||||
+ (isAngular ? '</code>' : '')
|
||||
@@ -146,7 +151,7 @@ Doc.prototype = {
|
||||
return parts.join('');
|
||||
},
|
||||
|
||||
parse: function(){
|
||||
parse: function() {
|
||||
var atName;
|
||||
var atText;
|
||||
var match;
|
||||
@@ -175,7 +180,7 @@ Doc.prototype = {
|
||||
this['this'] = this.markdown(this['this']);
|
||||
return this;
|
||||
|
||||
function flush(){
|
||||
function flush() {
|
||||
if (atName) {
|
||||
var text = trim(atText.join('\n'));
|
||||
if (atName == 'param') {
|
||||
@@ -229,26 +234,24 @@ Doc.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
html: function(){
|
||||
html: function() {
|
||||
var dom = new DOM(),
|
||||
self = this;
|
||||
|
||||
dom.h(this.name, function(){
|
||||
notice('workInProgress', 'Work in Progress',
|
||||
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
|
||||
dom.h(this.name, function() {
|
||||
notice('deprecated', 'Deprecated API', self.deprecated);
|
||||
|
||||
if (self.ngdoc != 'overview') {
|
||||
dom.h('Description', self.description, dom.html);
|
||||
}
|
||||
dom.h('Dependencies', self.requires, function(require){
|
||||
dom.tag('code', function(){
|
||||
dom.tag('a', {href:"#!/api/angular.service." + require.name}, require.name);
|
||||
dom.tag('code', function() {
|
||||
dom.tag('a', {href: 'api/angular.service.' + require.name}, require.name);
|
||||
});
|
||||
dom.html(require.text);
|
||||
});
|
||||
|
||||
(self['html_usage_' + self.ngdoc] || function(){
|
||||
(self['html_usage_' + self.ngdoc] || function() {
|
||||
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
|
||||
}).call(self, dom);
|
||||
|
||||
@@ -271,10 +274,10 @@ Doc.prototype = {
|
||||
|
||||
html_usage_parameters: function(dom) {
|
||||
dom.h('Parameters', this.param, function(param){
|
||||
dom.tag('code', function(){
|
||||
dom.tag('code', function() {
|
||||
dom.text(param.name);
|
||||
if (param.optional) {
|
||||
dom.tag('i', function(){
|
||||
dom.tag('i', function() {
|
||||
dom.text('(optional');
|
||||
if(param['default']) {
|
||||
dom.text('=' + param['default']);
|
||||
@@ -293,7 +296,7 @@ Doc.prototype = {
|
||||
html_usage_returns: function(dom) {
|
||||
var self = this;
|
||||
if (self.returns) {
|
||||
dom.h('Returns', function(){
|
||||
dom.h('Returns', function() {
|
||||
dom.tag('code', '{' + self.returns.type + '}');
|
||||
dom.text('– ');
|
||||
dom.html(self.returns.description);
|
||||
@@ -314,8 +317,8 @@ Doc.prototype = {
|
||||
|
||||
html_usage_function: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.code(function(){
|
||||
dom.h('Usage', function() {
|
||||
dom.code(function() {
|
||||
dom.text(self.name.split('service.').pop());
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
@@ -330,8 +333,8 @@ Doc.prototype = {
|
||||
|
||||
html_usage_property: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.code(function(){
|
||||
dom.h('Usage', function() {
|
||||
dom.code(function() {
|
||||
dom.text(self.name);
|
||||
});
|
||||
|
||||
@@ -341,8 +344,8 @@ Doc.prototype = {
|
||||
|
||||
html_usage_directive: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
|
||||
dom.h('Usage', function() {
|
||||
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function() {
|
||||
dom.text('<' + self.element + ' ');
|
||||
dom.text(self.shortName);
|
||||
if (self.param.length) {
|
||||
@@ -357,9 +360,9 @@ Doc.prototype = {
|
||||
|
||||
html_usage_filter: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.tag('code', function(){
|
||||
dom.h('Usage', function() {
|
||||
dom.h('In HTML Template Binding', function() {
|
||||
dom.tag('code', function() {
|
||||
dom.text('{{ ');
|
||||
dom.text(self.shortName);
|
||||
dom.text('_expression | ');
|
||||
@@ -369,8 +372,8 @@ Doc.prototype = {
|
||||
});
|
||||
});
|
||||
|
||||
dom.h('In JavaScript', function(){
|
||||
dom.tag('code', function(){
|
||||
dom.h('In JavaScript', function() {
|
||||
dom.tag('code', function() {
|
||||
dom.text('angular.filter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('(');
|
||||
@@ -385,77 +388,29 @@ Doc.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_formatter: function(dom){
|
||||
html_usage_inputType: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
if (self.inputType=='select')
|
||||
dom.text('<select name="bindExpression"');
|
||||
else
|
||||
dom.text('<input type="text" name="bindExpression"');
|
||||
dom.text(' ng:format="');
|
||||
dom.text(self.shortName);
|
||||
self.parameters(dom, ':', false, true);
|
||||
dom.text('">');
|
||||
dom.h('Usage', function() {
|
||||
dom.code(function() {
|
||||
dom.text('<input type="' + self.shortName + '"');
|
||||
(self.param||[]).forEach(function(param){
|
||||
dom.text('\n ');
|
||||
dom.text(param.optional ? ' [' : ' ');
|
||||
dom.text(param.name);
|
||||
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."');
|
||||
dom.text(param.optional ? ']' : '');
|
||||
});
|
||||
dom.text('>');
|
||||
});
|
||||
|
||||
dom.h('In JavaScript', function(){
|
||||
dom.code(function(){
|
||||
dom.text('var userInputString = angular.formatter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('.format(modelValue');
|
||||
self.parameters(dom, ', ', false, true);
|
||||
dom.text(');');
|
||||
dom.text('\n');
|
||||
dom.text('var modelValue = angular.formatter.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('.parse(userInputString');
|
||||
self.parameters(dom, ', ', false, true);
|
||||
dom.text(');');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_validator: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
dom.text('<input type="text" ng:validate="');
|
||||
dom.text(self.shortName);
|
||||
self.parameters(dom, ':', true);
|
||||
dom.text('"/>');
|
||||
});
|
||||
});
|
||||
|
||||
dom.h('In JavaScript', function(){
|
||||
dom.code(function(){
|
||||
dom.text('angular.validator.');
|
||||
dom.text(self.shortName);
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
dom.text(')');
|
||||
});
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
},
|
||||
|
||||
html_usage_widget: function(dom){
|
||||
var self = this;
|
||||
dom.h('Usage', function(){
|
||||
dom.h('In HTML Template Binding', function(){
|
||||
dom.code(function(){
|
||||
dom.h('Usage', function() {
|
||||
dom.h('In HTML Template Binding', function() {
|
||||
dom.code(function() {
|
||||
if (self.shortName.match(/^@/)) {
|
||||
dom.text('<');
|
||||
dom.text(self.element);
|
||||
@@ -473,11 +428,11 @@ Doc.prototype = {
|
||||
dom.text('<');
|
||||
dom.text(self.shortName);
|
||||
(self.param||[]).forEach(function(param){
|
||||
if (param.optional) {
|
||||
dom.text(' [' + param.name + '="..."]');
|
||||
} else {
|
||||
dom.text(' ' + param.name + '="..."');
|
||||
}
|
||||
dom.text('\n ');
|
||||
dom.text(param.optional ? ' [' : ' ');
|
||||
dom.text(param.name);
|
||||
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."');
|
||||
dom.text(param.optional ? ']' : '');
|
||||
});
|
||||
dom.text('></');
|
||||
dom.text(self.shortName);
|
||||
@@ -499,8 +454,8 @@ Doc.prototype = {
|
||||
var self = this;
|
||||
|
||||
if (this.param.length) {
|
||||
dom.h('Usage', function(){
|
||||
dom.code(function(){
|
||||
dom.h('Usage', function() {
|
||||
dom.code(function() {
|
||||
dom.text(self.name.split('.').pop());
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
@@ -515,7 +470,7 @@ Doc.prototype = {
|
||||
|
||||
dom.h('Methods', this.methods, function(method){
|
||||
var signature = (method.param || []).map(property('name'));
|
||||
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
|
||||
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function() {
|
||||
dom.html(method.description);
|
||||
method.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
@@ -525,20 +480,26 @@ Doc.prototype = {
|
||||
});
|
||||
});
|
||||
dom.h('Properties', this.properties, function(property){
|
||||
dom.h(property.name, function(){
|
||||
dom.h(property.name, function() {
|
||||
dom.html(property.description);
|
||||
dom.h('Example', property.example, dom.html);
|
||||
});
|
||||
});
|
||||
dom.h('Events', this.events, function(event){
|
||||
dom.h(event.shortName, event, function(){
|
||||
dom.h(event.shortName, event, function() {
|
||||
dom.html(event.description);
|
||||
dom.tag('div', {class:'inline'}, function(){
|
||||
dom.h('Type:', event.type);
|
||||
});
|
||||
dom.tag('div', {class:'inline'}, function(){
|
||||
dom.h('Target:', event.target);
|
||||
});
|
||||
if (event.type == 'listen') {
|
||||
dom.tag('div', {class:'inline'}, function() {
|
||||
dom.h('Listen on:', event.target);
|
||||
});
|
||||
} else {
|
||||
dom.tag('div', {class:'inline'}, function() {
|
||||
dom.h('Type:', event.type);
|
||||
});
|
||||
dom.tag('div', {class:'inline'}, function() {
|
||||
dom.h('Target:', event.target);
|
||||
});
|
||||
}
|
||||
event.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
|
||||
@@ -570,23 +531,23 @@ function scenarios(docs){
|
||||
var specs = [];
|
||||
|
||||
specs.push('describe("angular+jqlite", function() {');
|
||||
appendSpecs('index.html');
|
||||
appendSpecs('index-nocache.html#!/');
|
||||
specs.push('});');
|
||||
|
||||
specs.push('');
|
||||
specs.push('');
|
||||
|
||||
specs.push('describe("angular+jquery", function() {');
|
||||
appendSpecs('index-jq.html');
|
||||
appendSpecs('index-jq-nocache.html#!/');
|
||||
specs.push('});');
|
||||
|
||||
return specs.join('\n');
|
||||
|
||||
function appendSpecs(htmlFile) {
|
||||
function appendSpecs(urlPrefix) {
|
||||
docs.forEach(function(doc){
|
||||
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
|
||||
specs.push(' beforeEach(function(){');
|
||||
specs.push(' browser().navigateTo("' + htmlFile + '#!/' + doc.section + '/' + doc.id + '");');
|
||||
specs.push(' describe("' + doc.section + '/' + doc.id + '", function() {');
|
||||
specs.push(' beforeEach(function() {');
|
||||
specs.push(' browser().navigateTo("' + urlPrefix + doc.section + '/' + doc.id + '");');
|
||||
specs.push(' });');
|
||||
specs.push(' ');
|
||||
doc.scenarios.forEach(function(scenario){
|
||||
@@ -632,10 +593,9 @@ var KEYWORD_PRIORITY = {
|
||||
'.angular.Object': 7,
|
||||
'.angular.directive': 7,
|
||||
'.angular.filter': 7,
|
||||
'.angular.formatter': 7,
|
||||
'.angular.scope': 7,
|
||||
'.angular.service': 7,
|
||||
'.angular.validator': 7,
|
||||
'.angular.inputType': 7,
|
||||
'.angular.widget': 7,
|
||||
'.angular.mock': 8,
|
||||
'.dev_guide.overview': 1,
|
||||
@@ -724,7 +684,7 @@ function indent(text, spaceCount) {
|
||||
function merge(docs){
|
||||
var byFullId = {};
|
||||
|
||||
docs.forEach(function (doc) {
|
||||
docs.forEach(function(doc) {
|
||||
byFullId[doc.section + '/' + doc.id] = doc;
|
||||
});
|
||||
|
||||
|
||||
@@ -8,4 +8,8 @@
|
||||
|
||||
RewriteEngine on
|
||||
RewriteCond %{HTTP_COOKIE} ng-offline="NG_VERSION_FULL"
|
||||
RewriteRule appcache.manifest appcache-offline.manifest
|
||||
RewriteRule appcache.manifest appcache-offline.manifest
|
||||
|
||||
|
||||
## HTML5 URL Support ##
|
||||
RewriteRule ^(guide|api|cookbook|misc|tutorial)(/.*)?$ index.html
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(function(){
|
||||
(function() {
|
||||
|
||||
var angularJsUrl;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
@@ -56,8 +56,13 @@
|
||||
element.append(tabs);
|
||||
|
||||
var script = (exampleSrc.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
|
||||
|
||||
try {
|
||||
window.eval(script);
|
||||
if (window.execScript) { // IE
|
||||
window.execScript(script || '"stupid IE!"'); // IE complains when evaling empty string
|
||||
} else {
|
||||
window.eval(script);
|
||||
}
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
@@ -77,10 +82,12 @@
|
||||
|
||||
return '<form class="jsfiddle" method="post" action="' + fiddleUrl + '" target="_blank">' +
|
||||
'<textarea name="css">' +
|
||||
'.ng-invalid { border: 1px solid red; } \n' +
|
||||
'body { font-family: Arial,Helvetica,sans-serif; }\n' +
|
||||
'body, td, th { font-size: 14px; margin: 0; }\n' +
|
||||
'table { border-collapse: separate; border-spacing: 2px; display: table; margin-bottom: 0; margin-top: 0; -moz-box-sizing: border-box; text-indent: 0; }\n' +
|
||||
'a:link, a:visited, a:hover { color: #5D6DB6; text-decoration: none; }\n' +
|
||||
'.error { color: red; }\n' +
|
||||
'</textarea>' +
|
||||
'<input type="text" name="title" value="AngularJS Live Example">' +
|
||||
'<textarea name="html">' +
|
||||
|
||||
@@ -49,6 +49,10 @@ li {
|
||||
margin: 0.3em 0 0.3em 0;
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
border: 1px solid red;
|
||||
}
|
||||
|
||||
|
||||
/*----- Upgrade IE Prompt -----*/
|
||||
|
||||
@@ -425,3 +429,41 @@ li {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 4px;
|
||||
border: 1px solid lightgray;
|
||||
}
|
||||
|
||||
td.head, thead td, .head td {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
td.empty-corner-lt {
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* service.$location Html5 mode example */
|
||||
.html5-hashbang-example {
|
||||
height: 255px;
|
||||
margin-left: -40px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.html5-hashbang-example div {
|
||||
float: left;
|
||||
width: 375px;
|
||||
}
|
||||
|
||||
.html5-hashbang-example div input {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
+21
-12
@@ -4,7 +4,8 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
|
||||
var self = this,
|
||||
OFFLINE_COOKIE_NAME = 'ng-offline',
|
||||
HAS_HASH = /#/;
|
||||
DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/,
|
||||
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
|
||||
|
||||
this.$location = $location;
|
||||
|
||||
@@ -13,13 +14,14 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
self.subpage = false;
|
||||
self.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
|
||||
|
||||
if (!HAS_HASH.test($location.href)) {
|
||||
$location.hashPath = '!/api';
|
||||
if (!$location.path() || INDEX_PATH.test($location.path())) {
|
||||
$location.path('/api').replace();
|
||||
}
|
||||
|
||||
this.$watch('$location.hashPath', function(scope, hashPath) {
|
||||
if (hashPath.match(/^!/)) {
|
||||
var parts = hashPath.substring(1).split('/');
|
||||
this.$watch('$location.path()', function(scope, path) {
|
||||
// ignore non-doc links which are used in examples
|
||||
if (DOCS_PATH.test(path)) {
|
||||
var parts = path.split('/');
|
||||
self.sectionId = parts[1];
|
||||
self.partialId = parts[2] || 'index';
|
||||
self.pages = angular.Array.filter(NG_PAGES, {section:self.sectionId});
|
||||
@@ -39,11 +41,11 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
});
|
||||
|
||||
this.getUrl = function(page){
|
||||
return '#!/' + page.section + '/' + page.id;
|
||||
return page.section + '/' + page.id;
|
||||
};
|
||||
|
||||
this.getCurrentPartial = function(){
|
||||
return this.partialId ? ('./' + this.sectionId + '/' + this.partialId + '.html') : '';
|
||||
this.getCurrentPartial = function() {
|
||||
return this.partialId ? ('./partials/' + this.sectionId + '/' + this.partialId + '.html') : '';
|
||||
};
|
||||
|
||||
this.getClass = function(page) {
|
||||
@@ -67,13 +69,13 @@ function DocsController($location, $browser, $window, $cookies) {
|
||||
this.afterPartialLoaded = function() {
|
||||
SyntaxHighlighter.highlight();
|
||||
$window.scrollTo(0,0);
|
||||
$window._gaq.push(['_trackPageview', $location.hashPath.substr(1)]);
|
||||
$window._gaq.push(['_trackPageview', $location.path()]);
|
||||
};
|
||||
|
||||
this.getFeedbackUrl = function() {
|
||||
return "mailto:angular@googlegroups.com?" +
|
||||
"subject=" + escape("Feedback on " + $location.href) + "&" +
|
||||
"body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
|
||||
"subject=" + escape("Feedback on " + $location.absUrl()) + "&" +
|
||||
"body=" + escape("Hi there,\n\nI read " + $location.absUrl() + " and wanted to ask ....");
|
||||
};
|
||||
|
||||
/** stores a cookie that is used by apache to decide which manifest ot send */
|
||||
@@ -123,3 +125,10 @@ function TutorialInstructionsCtrl($cookieStore) {
|
||||
$cookieStore.put('selEnv', id);
|
||||
};
|
||||
}
|
||||
|
||||
angular.service('$locationConfig', function() {
|
||||
return {
|
||||
html5Mode: true,
|
||||
hashPrefix: '!'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,12 +5,66 @@
|
||||
doc:manifest>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
|
||||
<meta name="fragment" content="!">
|
||||
<link rel="stylesheet" href="docs-combined.css" type="text/css"/>
|
||||
<link rel="stylesheet" href="syntaxhighlighter/syntaxhighlighter-combined.css" type="text/css"/>
|
||||
<title ng:bind-template="AngularJS: {{partialTitle}}">AngularJS</title>
|
||||
<script type="text/javascript">
|
||||
// dynamically add base tag as well as css and javascript files.
|
||||
// we can't add css/js the usual way, because some browsers (FF) eagerly prefetch resources
|
||||
// before the base attribute is added, causing 404 and terribly slow loading of the docs app.
|
||||
(function() {
|
||||
var indexFile = (location.pathname.match(/\/(index[^\.]*\.html)/) || ['', ''])[1],
|
||||
rUrl = /(#!\/|api|guide|misc|tutorial|cookbook|index[^\.]*\.html).*$/,
|
||||
baseUrl = location.href.replace(rUrl, indexFile),
|
||||
jQuery = /index-jq[^\.]*\.html$/.test(baseUrl),
|
||||
debug = /index[^\.]*-debug\.html$/.test(baseUrl),
|
||||
angularPath = debug ? '../angular.js' : '../angular.min.js',
|
||||
headEl = document.getElementsByTagName('head')[0],
|
||||
sync = true;
|
||||
|
||||
addTag('base', {href: baseUrl});
|
||||
addTag('link', {rel: 'stylesheet', href: 'docs-combined.css', type: 'text/css'});
|
||||
addTag('link', {rel: 'stylesheet', href: 'syntaxhighlighter/syntaxhighlighter-combined.css',
|
||||
type: 'text/css'});
|
||||
addTag('script', {src: 'syntaxhighlighter/syntaxhighlighter-combined.js'}, sync);
|
||||
if (jQuery) addTag('script', {src: 'jquery.min.js'});
|
||||
addTag('script', {src: angularPath, 'ng:autobind':''}, sync);
|
||||
addTag('script', {src: 'docs-combined.js'}, sync);
|
||||
addTag('script', {src: 'docs-keywords.js'}, sync);
|
||||
|
||||
function addTag(name, attributes, sync) {
|
||||
var el = document.createElement(name),
|
||||
attrName;
|
||||
|
||||
for (attrName in attributes) {
|
||||
el.setAttribute(attrName, attributes[attrName]);
|
||||
}
|
||||
|
||||
sync ? document.write(outerHTML(el)) : headEl.appendChild(el);
|
||||
}
|
||||
|
||||
function outerHTML(node){
|
||||
// if IE, Chrome take the internal method otherwise build one
|
||||
return node.outerHTML || (
|
||||
function(n){
|
||||
var div = document.createElement('div'), h;
|
||||
div.appendChild(n);
|
||||
h = div.innerHTML;
|
||||
div = null;
|
||||
return h;
|
||||
})(node);
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
// force page reload when new update is available
|
||||
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
window.applicationCache.swapCache();
|
||||
window.location.reload();
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
<script>
|
||||
// GA asynchronous tracker
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-8594346-3']);
|
||||
@@ -21,14 +75,6 @@
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
// force page reload when new update is available
|
||||
window.applicationCache && window.applicationCache.addEventListener('updateready', function(e) {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
window.applicationCache.swapCache();
|
||||
window.location.reload();
|
||||
}
|
||||
}, false);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@@ -45,15 +91,15 @@
|
||||
|
||||
<ul id="navbar">
|
||||
<li><a href="http://angularjs.org/">AngularJS</a></li>
|
||||
<li><a href="#!/misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
|
||||
<li><a href="#!/tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
|
||||
<li><a href="#!/api" ng:class="selectedSection('api')">API Reference</a></li>
|
||||
<li><a href="#!/cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
|
||||
<li><a href="#!/guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
|
||||
<li><a href="misc/started" ng:class="selectedSection('misc')">Getting Started</a></li>
|
||||
<li><a href="tutorial" ng:class="selectedSection('tutorial')">Tutorial</a></li>
|
||||
<li><a href="api" ng:class="selectedSection('api')">API Reference</a></li>
|
||||
<li><a href="cookbook" ng:class="selectedSection('cookbook')">Examples</a></li>
|
||||
<li><a href="guide" ng:class="selectedSection('guide')">Developer Guide</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="sidebar">
|
||||
<input type="text" name="search" id="search-box" placeholder="search the docs"
|
||||
<input type="text" ng:model="search" id="search-box" placeholder="search the docs"
|
||||
tabindex="1" accesskey="s">
|
||||
|
||||
<ul id="content-list" ng:class="sectionId" ng:cloak>
|
||||
@@ -100,11 +146,5 @@
|
||||
<button id="cacheButton" ng:click="enableOffline()">Let me have them all!</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="syntaxhighlighter/syntaxhighlighter-combined.js"></script>
|
||||
<!-- jquery place holder -->
|
||||
<!-- angular script place holder -->
|
||||
<script src="docs-combined.js"></script>
|
||||
<script src="docs-keywords.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
../../../lib/jquery/jquery-1.4.2.min.js
|
||||
../../../lib/jquery/jquery.min.js
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+6
-6
@@ -8,17 +8,17 @@ var Q = require('qq');
|
||||
var OUTPUT_DIR = "build/docs/";
|
||||
var fs = require('fs');
|
||||
|
||||
exports.output = function(file, content){
|
||||
exports.output = function(file, content) {
|
||||
console.log('writing ', file);
|
||||
var fullPath = OUTPUT_DIR + file;
|
||||
var dir = parent(fullPath);
|
||||
return Q.when(exports.makeDir(dir), function(error) {
|
||||
qfs.write(fullPath,exports.toString(content));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//recursively create directory
|
||||
exports.makeDir = function (path) {
|
||||
exports.makeDir = function(path) {
|
||||
var parts = path.split(/\//);
|
||||
var path = ".";
|
||||
//Sequentially create directories
|
||||
@@ -99,7 +99,7 @@ function merge(srcs, to) {
|
||||
var contents = [];
|
||||
//Sequentially read file
|
||||
var done;
|
||||
srcs.forEach(function (src) {
|
||||
srcs.forEach(function(src) {
|
||||
done = Q.when(done, function(content) {
|
||||
if(content) contents.push(content);
|
||||
return qfs.read(src, 'b');
|
||||
@@ -128,7 +128,7 @@ exports.toString = function toString(obj) {
|
||||
return obj;
|
||||
case 'object':
|
||||
if (obj instanceof Array) {
|
||||
obj.forEach(function (value, key) {
|
||||
obj.forEach(function(value, key) {
|
||||
obj[key] = toString(value);
|
||||
});
|
||||
return obj.join('');
|
||||
@@ -140,4 +140,4 @@ exports.toString = function toString(obj) {
|
||||
};
|
||||
|
||||
|
||||
function noop(){};
|
||||
function noop() {};
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
<span><angular/> Buzz</span>
|
||||
<span>
|
||||
filter:
|
||||
<input type="text" name="filterText"/>
|
||||
<input type="text" ng:model="filterText"/>
|
||||
</span>
|
||||
<span>
|
||||
user:
|
||||
<input type="text" name="userId" ng:required/>
|
||||
<input type="text" ng:model="userId" required/>
|
||||
<button ng:click="$location.hashPath = userId">fetch</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -8,11 +8,11 @@ angular.service('myApplication', function($resource){
|
||||
});
|
||||
}, {inject:['$resource']});
|
||||
|
||||
function BuzzController(){
|
||||
function BuzzController() {
|
||||
this.$watch('$location.hashPath', this.userChange);
|
||||
}
|
||||
BuzzController.prototype = {
|
||||
userChange: function(){
|
||||
userChange: function() {
|
||||
this.userId = this.$location.hashPath;
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
@@ -22,7 +22,7 @@ BuzzController.prototype = {
|
||||
if (activity.replies) {
|
||||
activity.replies.show = !activity.replies.show;
|
||||
} else {
|
||||
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id}, function(){
|
||||
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id}, function() {
|
||||
activity.replies.show = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
scope = angular.compile(document);
|
||||
scope.$init();
|
||||
scope.a = 3;
|
||||
scope.$eval();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Quantity: <input type="text" name="a" value="2">
|
||||
*
|
||||
Cost: <input type="text" name="b" value="3.4">
|
||||
= {{a * b | currency}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../angular-minified.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
scope = angular.compile(document);
|
||||
scope.$init();
|
||||
scope.a = 3;
|
||||
scope.$eval();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Quantity: <input type="text" name="a" value="2">
|
||||
*
|
||||
Cost: <input type="text" name="b" value="3.4">
|
||||
= {{a * b | currency}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>
|
||||
<script type="text/javascript" src="../angular-debug.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
var scope = angular.compile(document);
|
||||
scope.a = 3;
|
||||
scope.$init();
|
||||
scope.$eval();
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Quantity: <input type="text" name="a" value="2">
|
||||
*
|
||||
Cost: <input type="text" name="b" value="3.4">
|
||||
= {{a * b | currency}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,55 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<script type="text/javascript" src="../lib/underscore/underscore.js"></script>
|
||||
<script type="text/javascript" src="../lib/jquery/jquery-1.3.2.js"></script>
|
||||
<script type="text/javascript" src="../src/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
var scope = angular.compile(document);
|
||||
scope.init();
|
||||
scope.set("add", function(){
|
||||
var partial = $("#partial");
|
||||
//id++;
|
||||
partial.html('<div>{{ error() }}<br/></div>');
|
||||
var scope = angular.compile(partial);
|
||||
scope.set("hello", function (){
|
||||
return 'who dat?';
|
||||
});
|
||||
scope.set("error", function (){
|
||||
this.misko.length;
|
||||
});
|
||||
function XXXXXXXXX(){};
|
||||
scope.set('xxx', new XXXXXXXXX());
|
||||
scope.set("names", ["adam", "misko", "shyam"]);
|
||||
scope.init();
|
||||
});
|
||||
scope.set("remove", function(){
|
||||
var partial = $("#partial");
|
||||
/*
|
||||
partial.find('*').andSelf().each(function(){
|
||||
var scope = $(this).data('scope');
|
||||
if (scope) {
|
||||
delete scope.state;
|
||||
delete scope.widgets;
|
||||
}
|
||||
var cntl = $(this).data('controller');
|
||||
if (cntl) {
|
||||
delete cntl.view;
|
||||
}
|
||||
$(this).removeData();
|
||||
});
|
||||
*/
|
||||
partial.removeData('scope');
|
||||
partial.children().remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<link rel="StyleSheet" type="text/css" href="../css/angular.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<input type="button" value="add" ng:click="add()"/>
|
||||
<input type="button" value="remove" ng:click="remove()"/>
|
||||
<div id="partial"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -12,7 +12,7 @@
|
||||
<body ng:controller="example.personalLog.LogCtrl">
|
||||
|
||||
<form action="" ng:submit="addLog(newMsg)">
|
||||
<input type="text" name="newMsg" />
|
||||
<input type="text" ng:model="newMsg" />
|
||||
<input type="submit" value="add" />
|
||||
<input type="button" value="remove all" ng:click="rmLogs()" />
|
||||
</form>
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('personal log', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should create new logs and order them in reverse chronological order', function(){
|
||||
it('should create new logs and order them in reverse chronological order', function() {
|
||||
//create first msg
|
||||
input('newMsg').enter('my first message');
|
||||
element('form input[type="submit"]').click();
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<link rel="stylesheet" type="text/css" href="../../css/angular.css">
|
||||
<script type="text/javascript" src="../../lib/underscore/underscore.js"></script>
|
||||
<script type="text/javascript" src="../../lib/jquery/jquery-1.4.2.min.js"></script>
|
||||
<script type="text/javascript" src="../../lib/jquery/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="../../src/angular-bootstrap.js"></script>
|
||||
<script type="text/javascript" src="tweeterclient.js"></script>
|
||||
</head>
|
||||
<body ng:class="status" ng:init="mute={}" ng:watch="$anchor.user: tweets = fetchTweets($anchor.user)">
|
||||
<div class="addressbook box">
|
||||
<h1>Address Book</h1>
|
||||
[ Filter: <input type="text" name="userFilter"/>]
|
||||
[ Filter: <input type="text" ng:model="userFilter"/>]
|
||||
<ul>
|
||||
<li ng:repeat="user in users.$filter(userFilter).$orderBy('screen_name')" ng:class-even="'even'" ng:class-odd="'odd'">
|
||||
<a href="" ng:click="$anchor.user=user.screen_name"><img src="{{user.profile_image_url}}"/></a>
|
||||
@@ -29,13 +29,13 @@
|
||||
<div ng:show="$anchor.edituser" ng:eval="user = users.$find({:$.screen_name == $anchor.edituser})">
|
||||
<div class="editor">
|
||||
<label>Username:</label>
|
||||
<input type="text" name="user.screen_name" disabled="disabled"/>
|
||||
<input type="text" ng:model="user.screen_name" disabled="disabled"/>
|
||||
<label>Name:</label>
|
||||
<input type="text" name="user.name"/>
|
||||
<input type="text" ng:model="user.name"/>
|
||||
<label>Image:</label>
|
||||
<input type="text" name="user.profile_image_url"/>
|
||||
<input type="text" ng:model="user.profile_image_url"/>
|
||||
<label>Notes:</label>
|
||||
<textarea type="text" name="user.notes"></textarea>
|
||||
<textarea type="text" ng:model="user.notes"></textarea>
|
||||
|
||||
<input type="button" ng:click="$anchor.edituser=undefined" value="Close"/>
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@ tweets={{tweets}}
|
||||
</div>
|
||||
<div class="tweeter box">
|
||||
<h1>Tweets: {{$anchor.user}}</h1>
|
||||
[ Filter: <input type="text" name="tweetFilter"/>
|
||||
[ Filter: <input type="text" ng:model="tweetFilter"/>
|
||||
<span ng:show="$anchor.user">| <a href="#user="><< All</a></span>
|
||||
]
|
||||
<div class="loading">Loading...</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user