Compare commits

...

67 Commits

Author SHA1 Message Date
Igor Minar eabedba34d chore(release): cutting the 0.10.4 human-torch release 2011-10-22 21:39:39 -07:00
Igor Minar b4add97c17 docs(changelog): update changelog with 0.10.4 release notes 2011-10-22 21:39:39 -07:00
Igor Minar bacc31bea9 fix(defer.cancel): should return false instead of undefined 2011-10-22 21:32:48 -07:00
Igor Minar ad90c3574f feat($defer): add $defer.cancel
This functionality was previously available only as obscure $browser.defer.cancel.

I also added docs and tests and fixed an issue in .defer.cancel mock.
2011-10-22 21:32:48 -07:00
Igor Minar e28171d5e4 fix(docs): key-binding used old scope apis 2011-10-22 21:32:48 -07:00
Igor Minar ce73ed091b feat(docs): add "Loading..." notification 2011-10-22 21:32:47 -07:00
Igor Minar 90ac8d57b0 fix(docs): update page title only when content loads 2011-10-22 21:32:47 -07:00
Igor Minar 6eb1179505 style(docs): change "this" to "scope" in the controller 2011-10-22 21:32:47 -07:00
Vojta Jina 9b85757102 fix($location): rewrite links with nested elements
For example:
<a href="some/link">inner <span>text</span></a>

If you click on "text", then the span element is event.target, so we need to traverse the DOM.
2011-10-22 15:35:18 -07:00
Igor Minar c6c3949b14 feat(filter.date): use mediumDate as default
Breaking change!

Previously the default was fullDate.
2011-10-20 16:51:28 -07:00
Igor Minar e175db37c6 fix(date filter): default to fullDate format
The browser's behave inconsistently, so we should just stick to one format
when the format is not specified by the developer

Closes #605
2011-10-20 16:51:28 -07:00
Igor Minar f38010d3a2 fix(compiler): revert 8611ebe6 - calling \$digest after linking
Change introduced by me in 8611ebe6 results in considerable inefficiencies when the compiler
and linker is used from within a widget, in which case, we call $digest unnecessary since it
will be called by the $apply which called the directive/widget in the first place.

There are only two places when the extra $digest call can be useful - when manually bootstrapping
the app or in tests. However even in tests this behavior can result in unwanted results (especially
when ng:controller is involved). So it is better to leave it for the developer to call $digest
when it is really needed.
2011-10-20 15:51:14 -07:00
Misko Hevery 7fc18b263d fix(radio): allows data-binding on value property. Closes#316 2011-10-20 11:30:40 -07:00
Igor Minar fabc9f77a3 feat(sanitizer): add html5 elements to the whitelist
Closes #89
2011-10-20 09:44:52 -07:00
Igor Minar c17c731fdc style(select): cleaning up select.js 2011-10-19 22:52:14 -07:00
TEHEK Firefox 3692885810 fix(ng:options): compile null/blank option tag
Fixes #562
2011-10-19 22:52:14 -07:00
Igor Minar 5d43439dbe fix(ng:pattern): correctly parse out inlined regexp 2011-10-19 21:52:20 -07:00
Igor Minar a46f2a0db3 docs(textarea): add docs for angular.widget.textarea 2011-10-19 16:49:34 -07:00
Igor Minar 3217a249e1 style(input): fix style violations in the input.js file 2011-10-19 16:49:34 -07:00
Konstantin Stepanov 78f394fd17 feat(input): add ng:minlength and ng:maxlength validation
notes(igor): I also e2e tests and refactorred the e2e test example to be
more clear about what is a variable and what is an html/framework api.
2011-10-19 16:49:20 -07:00
Konstantin Stepanov e82e64d57b fix(input): recognize 'password' as an html input type 2011-10-19 10:36:36 -07:00
Vojta Jina 8978e066b5 fix(gen-docs): require files without touching PATH
So that it works on latest revision of node...
New version of Node (v0.5.x) does not support require.paths.push().
2011-10-18 22:23:52 -07:00
Igor Minar 833eb3c844 fix(ng:repeat): repeater should ignore $ and $$ properties 2011-10-18 17:27:43 -07:00
Igor Minar 07926ff1ef chore(version.yaml): add missing snapshot version suffix 2011-10-18 17:25:10 -07:00
Igor Minar e801faba2e chore(jstd adapter): switch to our version with backported fixes
sha of the version: da92db714142b49f9cf61db664e782bb0ccad80b
2011-10-18 16:21:52 -07:00
TEHEK Firefox ee6af9a978 fix(ng:options): select correct element when '?'-option was previously selected
Closes #599
2011-10-18 14:02:54 -07:00
Igor Minar 74379df6c4 chore(release): preparing the 0.10.4 human-torch iteration 2011-10-14 15:29:17 -07:00
Igor Minar fe65dd926c chore(release): cutting the 0.10.3 shattering-heartbeat release 2011-10-14 08:31:39 -07:00
Igor Minar 669b53ede2 fix(docs): fix jsfiddle integration
this got accidentally messed up during the forms refactoring and mass renaming
2011-10-14 08:31:00 -07:00
Igor Minar b0c3f28e8f docs(forms): fix devguide forms example code 2011-10-14 08:30:02 -07:00
Igor Minar 9810dc0993 docs(cookbook): disable jsfiddle for deeplinking example
it uses extra resources, so it won't work on fiddle
2011-10-14 08:29:33 -07:00
Vojta Jina ab5df20dfa chore(libs): update libs (jasmine, jstd, jasmine-jstd adapter) 2011-10-13 17:36:11 -07:00
Igor Minar d83a92c121 fix(checkbox): prefix true-value & false-value with ng: 2011-10-13 17:35:00 -07:00
Igor Minar d0425de29e chore(release): preparing release notes for 0.10.3 2011-10-13 16:54:30 -07:00
Igor Minar ad5e42cf82 docs($location): Html5 -> HTML5 2011-10-13 14:42:49 -07:00
Igor Minar 9ed1126adb docs($location): update replace() docs 2011-10-13 14:37:37 -07:00
Igor Minar 7a19eb84aa docs($location): fix $config -> $locationConfig in docs 2011-10-13 14:07:48 -07:00
Igor Minar 718741acab chore(logo): add hi-res ng logo 2011-10-13 13:44:38 -07:00
Vojta Jina ec8bb675b4 fix(docs): set proper base href when hashbang url requested 2011-10-13 11:10:20 -07:00
Vojta Jina 8e32f3fd35 fix(nodeserver): docs rewriting
There can be url /api which does not end with / and we want to rewrite this url as well...
2011-10-13 11:10:20 -07:00
Vojta Jina 02332107e5 feat(nodeserver): if index.html exists, serve it instead of directory listing 2011-10-13 11:10:20 -07:00
Vojta Jina afc81b554e fix($location): do not rewrite link when meta key pressed 2011-10-13 11:10:19 -07:00
Vojta Jina 26e8ab3693 feat(scenario): allow key pressing when triggering browser event
Add parameter to our browserTriger function to allow specifying which keys are pressed.

Note, this does not work on IE<9 !
2011-10-13 11:10:19 -07:00
Igor Minar 28ccc76aa1 docs(dev_guide.bootstrap.auto_bootstrap): fixing a typo 2011-10-12 23:04:48 -07:00
Igor Minar b3c4cb7cff docs(*): remove @workInProgress from everywhere
it's not useful any more and it only makes the docs look ugly
2011-10-12 23:04:48 -07:00
Misko Hevery 4af4378b11 fix(forms): broken tests on jQuery and ie8&9 2011-10-12 23:04:48 -07:00
Igor Minar 8611ebe6a0 fix(compiler): linking function should call $digest
The linked scope should be $digest-ed but only if a $digest isn't
already running on it.
2011-10-12 23:04:48 -07:00
Igor Minar 8f46a3c9ac fix(jqLite): attr for boolean attribute should lowercase value 2011-10-12 23:04:47 -07:00
Igor Minar 66fdb36ecb refactor(ng:bind-attr): simplify impl by leveraging jquery 2011-10-12 23:04:47 -07:00
Igor Minar f0f5ffa9aa test(jstd-config): add widget specs to jsTestDriver-jquery.conf 2011-10-12 23:04:47 -07:00
Igor Minar 2bc7afd3ba fix(ng:class): ignore undefined or NaN classnames 2011-10-12 23:04:47 -07:00
Igor Minar a4b45397e0 docs(forms): add ng:change docs and other fixes 2011-10-12 11:49:42 -07:00
Igor Minar de4e06ed73 test(checkbox): add test for ng:change 2011-10-12 11:11:10 -07:00
Misko Hevery fd822bdaf9 chore(formating): clean code to be function() { 2011-10-11 11:01:46 -07:00
Misko Hevery 4f78fd692c feat(forms): new and improved forms 2011-10-11 11:01:45 -07:00
Misko Hevery df6d2ba326 style(examples): clean up dead examples 2011-10-11 10:53:07 -07:00
Misko Hevery ccda436f94 style(.gitignore) added IDEA 2011-10-11 10:53:07 -07:00
Misko Hevery e86c435349 refactor(bindings): remove the decoration of the DOM with errors.
Only $exceptionHandler gets notified now.
2011-10-11 10:53:07 -07:00
Misko Hevery 1942861472 refactor(hover): delete hover service 2011-10-11 10:53:06 -07:00
Misko Hevery b96e978178 fix(jqlite): removeClass would clobber class names 2011-10-11 10:53:06 -07:00
Misko Hevery bda2bba2be feat(jqlite): added .inheritedData method and $destroy event.
- refactored .scope() to use .inheritedData() instead.
- .bind('$destroy', callback) will call when the DOM element is removed
2011-10-11 10:53:05 -07:00
Misko Hevery ca08c004c8 feat(jqlite): support required as a no-value attribute 2011-10-11 10:53:05 -07:00
Misko Hevery 25a62b58db refactor(injection) infer injection args in ng:controller only
Because only controllers don't have currying, we can infer its arguments, all other APIs needing currying, automatic inference complicates the matters unecessary.
2011-10-11 10:53:04 -07:00
Misko Hevery 97e3ec4d1b style(gitignore): added xproject to ignore list 2011-10-11 10:53:04 -07:00
Misko Hevery 75f11f1fc4 feat(ng:repeat) collection items and DOM elements affinity / stability 2011-10-11 10:53:04 -07:00
Misko Hevery e134a8335f fix(filter): make json filter ignore private properties 2011-10-11 10:53:03 -07:00
Igor Minar 8ee32a75f0 chore(release): prepare the 0.10.3 shattering-heartbeat iteration 2011-10-10 11:23:08 -07:00
226 changed files with 9147 additions and 5827 deletions
+2
View File
@@ -11,3 +11,5 @@ performance/temp*.html
angular.js.tmproj
node_modules
jsTestDriver*.conf
angular.xcodeproj
.idea
+134 -29
View File
@@ -1,7 +1,109 @@
- The Latest Stable Release: <a href="#0.9.19">0.9.19 canine-psychokinesis</a>
- The Latest Unstable Release: <a href="#0.10.2">0.10.2 sneaky-seagull</a>
- The Latest Unstable Release: <a href="#0.10.4">0.10.4 human-torch</a>
<a name="0.10.2"><a/>
<a name="0.10.4"></a>
# 0.10.4 human-torch (2011-10-22) #
## Features:
- New validation options for
[input widgets](http://docs-next.angularjs.org/api/angular.widget.input): `ng:minlength` and
`ng:maxlength`
([commit](https://github.com/angular/angular.js/commit/78f394fd17be581c84ecd526bb786ed1681d35cb))
(contributed by Konstantin Stepanov)
- HTML sanitizer was updated to recognize all safe HTML5 elements
(Issue [#89](https://github.com/angular/angular.js/issues/89))
- [ng:options]' blank option is now compiled and data-bound as any other template
(Issue [#562](https://github.com/angular/angular.js/issues/562))
(contributed by tehek)
- [$defer](http://docs-next.angularjs.org/api/angular.service.$defer) service now exposes `cancel`
method for task cancellation
([commit](https://github.com/angular/angular.js/commit/ad90c3574f8365ee4a1a973d5e43c64fe9fcda2c))
## Bug Fixes:
- [ng:options] should select correct element when '?'-option (invalid value) was previously selected
(Issue [#599](https://github.com/angular/angular.js/issues/599)) (contributed by Tehek)
- Fix data-binding of radio button's value property
(Issue [#316](https://github.com/angular/angular.js/issues/316))
- Input with type `password` should no be turned into a readable text field
([commit](https://github.com/angular/angular.js/commit/e82e64d57b65d9f3c4f2e8831f30b615a069b7f6))
(contributed by Konstantin Stepanov)
- [ng:repeat] should ignore object properties starting with `$`
([commit](https://github.com/angular/angular.js/commit/833eb3c84445110dc1dad238120573f08ed8d102))
- Correctly parse out inlined regexp from the input field's `ng:pattern` attribute.
([commit](https://github.com/angular/angular.js/commit/5d43439dbe764a4c7227f51b34a81b044f13901b))
- $location service in html5 mode should correctly rewrite links that contain nested elements
([commit](https://github.com/angular/angular.js/commit/9b85757102fbd44e88d0a3909fdf8b90f191b593))
## Breaking Changes:
- the [date] filter now uses 'mediumDate' format if none is specified. This was done to deal with
browser inconsistencies (each browser used to use different format)
(Issue [#605](https://github.com/angular/angular.js/issues/605),
[commit](https://github.com/angular/angular.js/commit/c6c3949b14f4003ecab291243edfca61262f2c3d),
[commit](https://github.com/angular/angular.js/commit/e175db37c6f52bba4080efeec22a7120a896099e))
- calling the linker function returned by [angular.compile][compile] doesn't automatically run
`$digest` on the linked scope any more. This behavior was briefly introduced in 0.10.3 but was
causing issues and inefficiencies in production apps so we reverted it. See:
[commit](https://github.com/angular/angular.js/commit/f38010d3a2f457a53798212ef72418637dabe189)
<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:
@@ -46,10 +148,13 @@
- 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/>
<a name="0.10.1"></a>
# 0.10.1 inexorable-juggernaut (2011-09-09) #
## Features
@@ -82,7 +187,7 @@
- $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)
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
@@ -95,7 +200,7 @@
<a name="0.10.0"><a/>
<a name="0.10.0"></a>
# 0.10.0 chicken-hands (2011-09-02) #
## Features
@@ -150,7 +255,7 @@
<a name="0.9.19"><a/>
<a name="0.9.19"></a>
# 0.9.19 canine-psychokinesis (2011-08-20) #
## Features
@@ -186,7 +291,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
@@ -219,7 +324,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]
@@ -256,7 +361,7 @@
<a name="0.9.17"><a/>
<a name="0.9.17"></a>
# <angular/> 0.9.17 vegetable-reanimation (2011-06-30) #
### New Features
@@ -291,7 +396,7 @@
<a name="0.9.16"><a/>
<a name="0.9.16"></a>
# <angular/> 0.9.16 weather-control (2011-06-07) #
### Features
@@ -322,7 +427,7 @@
<a name="0.9.15"><a/>
<a name="0.9.15"></a>
# <angular/> 0.9.15 lethal-stutter (2011-04-11) #
### Features
@@ -341,7 +446,7 @@
<a name="0.9.14"><a/>
<a name="0.9.14"></a>
# <angular/> 0.9.14 key-maker (2011-04-01) #
### Performance
@@ -362,12 +467,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)
@@ -389,7 +494,7 @@
<a name="0.9.12"><a/>
<a name="0.9.12"></a>
# <angular/> 0.9.12 thought-implanter (2011-03-03) #
### API
@@ -446,7 +551,7 @@
<a name="0.9.11"><a/>
<a name="0.9.11"></a>
# <angular/> 0.9.11 snow-maker (2011-02-08) #
### Documentation
@@ -477,7 +582,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
@@ -501,7 +606,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
@@ -532,7 +637,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
@@ -577,7 +682,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
@@ -591,7 +696,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
@@ -610,7 +715,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
@@ -640,7 +745,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
@@ -650,7 +755,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
@@ -667,7 +772,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
@@ -695,7 +800,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
@@ -733,7 +838,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
@@ -760,7 +865,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
+1 -60
View File
@@ -77,66 +77,8 @@ 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',
@@ -193,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'),
+9 -7
View File
@@ -12,15 +12,12 @@ angularFiles = {
'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/formFactory.js',
'src/service/location.js',
'src/service/log.js',
'src/service/resource.js',
@@ -36,6 +33,9 @@ angularFiles = {
'src/directives.js',
'src/markups.js',
'src/widgets.js',
'src/widget/form.js',
'src/widget/input.js',
'src/widget/select.js',
'src/AngularPublic.js',
],
@@ -57,7 +57,7 @@ angularFiles = {
],
'jstd': [
'lib/jasmine-1.0.1/jasmine.js',
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'lib/jquery/jquery.js',
'test/jquery_remove.js',
@@ -75,6 +75,7 @@ angularFiles = {
'test/jstd-scenario-adapter/*.js',
'test/*.js',
'test/service/*.js',
'test/widget/*.js',
'example/personalLog/test/*.js'
],
@@ -93,7 +94,7 @@ angularFiles = {
],
'jstdPerf': [
'lib/jasmine-1.0.1/jasmine.js',
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'angularSrc',
'src/angular-mocks.js',
@@ -109,7 +110,7 @@ angularFiles = {
],
'jstdJquery': [
'lib/jasmine-1.0.1/jasmine.js',
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'lib/jquery/jquery.js',
'test/jquery_alias.js',
@@ -127,6 +128,7 @@ angularFiles = {
'test/jstd-scenario-adapter/*.js',
'test/*.js',
'test/service/*.js',
'test/widget/*.js',
'example/personalLog/test/*.js'
],
-82
View File
@@ -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;
}
+92
View File
@@ -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>
-3
View File
@@ -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
-2
View File
@@ -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
+32 -32
View File
@@ -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>
+2 -2
View File
@@ -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">
+5 -6
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Deep Linking
@description
@@ -38,7 +37,7 @@ The two partials are defined in the following URLs:
* <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) {
@@ -56,7 +55,7 @@ The two partials are defined in the following URLs:
function WelcomeCntl($route){}
WelcomeCntl.prototype = {
greet: function(){
greet: function() {
alert("Hello " + this.person.name);
}
};
@@ -67,11 +66,11 @@ The two partials are defined in the following URLs:
this.cancel();
}
SettingsCntl.prototype = {
cancel: function(){
cancel: function() {
this.form = angular.copy(this.person);
},
save: function(){
save: function() {
angular.copy(this.form, this.person);
this.$location.path('/welcome');
}
@@ -89,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();
+27 -25
View File
@@ -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"]').prop('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"]').prop('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"]').prop('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"]').prop('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
+12 -6
View File
@@ -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
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook
@description
+4 -5
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: MVC
@description
@@ -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,7 +56,7 @@ 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(','));
@@ -91,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
@@ -18,7 +17,7 @@ explicitly.
<script src="http://code.angularjs.org/angular.js"></script>
<script>
angular.element(document).ready(function() {
angular.compile(document)();
angular.compile(document)().$apply();
});
</script>
</head>
@@ -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
View File
@@ -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
+25 -11
View File
@@ -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');
+610
View File
@@ -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 &lt;form&gt;} 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>&lt;input type="text" ng:model="input1"&gt;</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>&lt;textarea ng:model="input2"&gt;&lt;/textarea&gt;</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>
&lt;input type="radio" ng:model="input3" value="A"&gt;<br>
&lt;input type="radio" ng:model="input3" value="B"&gt;
</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>&lt;input type="checkbox" ng:model="input4"&gt;</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>
&lt;select ng:model="input5"&gt;<br>
&nbsp;&nbsp;&lt;option value="c"&gt;C&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="d"&gt;D&lt;/option&gt;<br>
&lt;/select&gt;<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>
&lt;select ng:model="input6" multiple size="4"&gt;<br>
&nbsp;&nbsp;&lt;option value="e"&gt;E&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="f"&gt;F&lt;/option&gt;<br>
&lt;/select&gt;<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
View File
@@ -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
+27 -19
View File
@@ -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 }}`:
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Using $location
@description
@@ -55,7 +54,7 @@ changes to $location are reflected into the browser address bar.
</tr>
<tr>
<td class="head">seamless integration with html5 API</td>
<td class="head">seamless integration with HTML5 API</td>
<td>no</td>
<td>yes (with a fallback for legacy browsers)</td>
</tr>
@@ -89,11 +88,11 @@ 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 `$config` service which is an object with
configuration properties:
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 />
`true` - see HTML5 mode<br />
`false` - see Hashbang mode<br />
default: `false`
@@ -103,7 +102,7 @@ configuration properties:
### Example configuration
<pre>
angular.service('$config', function() {
angular.service('$locationConfig', function() {
return {
html5mode: true,
hashPrefix: '!'
@@ -127,16 +126,25 @@ All of the setter methods return the same `$location` object to allow chaining.
change multiple segments in one go, chain setters like this:
<pre>$location.path('/newValue').search({key: value});</pre>
All setter methods take an optional boolean flag parameter, which signifies whether current history
record should be replaced or if a new record should be created (default). To change the current URL
without creating a new browser history record you can call:
<pre>$location.path('/newVal', true);</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 `$flush` phase. Since
any of the setters can take the replace flag, it's enough for one setter to use this flag in order
to make the entire "commit" a replace operation rather than addition to the browser history.
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
@@ -151,11 +159,11 @@ encoded.
`/path?search=a&b=c#hash`. The segments are encoded as well.
# Hashbang and Html5 Modes
# 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
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.
@@ -167,7 +175,7 @@ facilitate the browser URL change and history management.
<tr>
<td class="empty-corner-lt"></td>
<td>Hashbang mode</td>
<td>Html5 mode</td>
<td>HTML5 mode</td>
</tr>
</thead>
@@ -206,7 +214,7 @@ In this mode, `$location` uses Hashbang URLs in all browsers.
### Example
<pre>
angular.service('$config', function() {
angular.service('$locationConfig', function() {
return {
html5Mode: false,
hashPrefix: '!'
@@ -254,14 +262,14 @@ having to worry about whether the browser displaying your app supports the histo
### Example
<pre>
angular.service('$config', function() {
angular.service('$locationConfig', function() {
return {
html5Mode: true,
hashPrefix: '!'
};
});
// in browser with html5 history support:
// 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'
@@ -343,7 +351,7 @@ takes care of all relative link issues. **Otherwise you have to specify &lt;base
### Sending links among different browsers
Because of rewriting capability in Html5 mode, your users will be able to open regular url links in
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.
@@ -457,7 +465,7 @@ ng:ext-link>external</a>
function initEnv(name) {
var root = angular.element(document.getElementById(name + '-mode'));
var scope = angular.scope(null, {
$config: {html5Mode: true, hashPrefix: '!'},
$locationConfig: {html5Mode: true, hashPrefix: '!'},
$browser: browsers[name],
$document: root,
$sniffer: {history: name == 'html5'}
@@ -612,7 +620,7 @@ https://github.com/angular/angular.js/issues/404 issue}). If you should require
you will need to specify an extra property that has two watchers. For example:
<pre>
<!-- html -->
<input type="text" name="locationPath" />
<input type="text" ng:model="locationPath" />
</pre>
<pre>
// js - controller
@@ -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
@@ -1,51 +1,34 @@
@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.
Angular sets these CSS classes. It is up to your application to provide useful styling.
# Built-in CSS classes
# CSS classes used by angular
* `ng-exception`
* `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).
**Usage:** angular applies this class to a DOM element if that element contains an Expression that
threw an exception when evaluated.
* `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`.
**Styling:** The built-in styling of the ng-exception class displays an error message surrounded
by a solid red border, for example:
# Marking CSS classes
<div class="ng-exception">Error message</div>
* `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.
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>
* 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.templates.formatters Angular Formatters}
* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters}
* {@link dev_guide.forms Angular Forms}
@@ -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>
+3 -7
View File
@@ -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}
+14 -15
View File
@@ -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 -3
View File
@@ -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
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Downloading
@description
-1
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name FAQ
@description
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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)">
+3 -3
View File
@@ -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() {
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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);
+4 -4
View File
@@ -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

+7 -7
View File
@@ -1,15 +1,15 @@
var DOM = require('dom.js').DOM;
var DOM = require('../src/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>');
+50 -82
View File
@@ -1,11 +1,11 @@
var ngdoc = require('ngdoc.js');
var DOM = require('dom.js').DOM;
var ngdoc = require('../src/ngdoc.js');
var DOM = require('../src/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&lt;&gt;\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>&lt;angular/&gt;</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([{
@@ -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).
@@ -442,7 +442,7 @@ describe('ngdoc', function(){
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();
@@ -452,14 +452,14 @@ describe('ngdoc', function(){
});
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',
+5 -5
View File
@@ -1,9 +1,9 @@
var SiteMap = require('SiteMap.js').SiteMap;
var Doc = require('ngdoc.js').Doc;
var SiteMap = require('../src/SiteMap.js').SiteMap;
var Doc = require('../src/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>',
+2 -4
View File
@@ -1,8 +1,6 @@
if (global.jasmine) return;
require.paths.push(__dirname + "/../../lib");
require.paths.push(__dirname + '/../src');
var jasmine = require('jasmine-1.0.1');
var jasmine = require('../../lib/jasmine-1.0.1');
var sys = require('util');
for(var key in jasmine) {
@@ -10,7 +8,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'
+6 -6
View File
@@ -1,17 +1,17 @@
var writer = require('writer.js');
describe('writer', function(){
describe('toString', function(){
var writer = require('../src/writer.js');
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
View File
@@ -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">');
+3 -3
View File
@@ -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 {
+8 -10
View File
@@ -1,13 +1,11 @@
require.paths.push(__dirname);
require.paths.push('lib');
var reader = require('reader.js'),
ngdoc = require('ngdoc.js'),
writer = require('writer.js'),
SiteMap = require('SiteMap.js').SiteMap,
appCache = require('appCache.js').appCache,
var reader = require('./reader.js'),
ngdoc = require('./ngdoc.js'),
writer = require('./writer.js'),
SiteMap = require('./SiteMap.js').SiteMap,
appCache = require('./appCache.js').appCache,
Q = require('qq');
process.on('uncaughtException', function (err) {
process.on('uncaughtException', function(err) {
console.error(err.stack || err);
});
@@ -93,6 +91,6 @@ function writeTheRest(writesFuture) {
}
function now(){ return new Date().getTime(); }
function now() { return new Date().getTime(); }
function noop(){};
function noop() {};
+71 -111
View File
@@ -2,9 +2,9 @@
* All parsing/transformation code goes here. All code here should be sync to ease testing.
*/
var Showdown = require('showdown').Showdown;
var DOM = require('dom.js').DOM;
var htmlEscape = require('dom.js').htmlEscape;
var Showdown = require('../../lib/showdown').Showdown;
var DOM = require('./dom.js').DOM;
var htmlEscape = require('./dom.js').htmlEscape;
var NEW_LINE = /\n\r?/;
exports.trim = trim;
@@ -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,
@@ -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('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);
@@ -584,8 +545,8 @@ function scenarios(docs){
function appendSpecs(urlPrefix) {
docs.forEach(function(doc){
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
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(' ');
@@ -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;
});
+1 -2
View File
@@ -5,8 +5,7 @@
exports.collect = collect;
require.paths.push(__dirname);
var ngdoc = require('ngdoc.js'),
var ngdoc = require('./ngdoc.js'),
Q = require('qq'),
qfs = require('q-fs');
+3 -1
View File
@@ -1,4 +1,4 @@
(function(){
(function() {
var angularJsUrl;
var scripts = document.getElementsByTagName("script");
@@ -82,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">' +
+35 -2
View File
@@ -49,6 +49,10 @@ li {
margin: 0.3em 0 0.3em 0;
}
.ng-invalid {
border: 1px solid red;
}
/*----- Upgrade IE Prompt -----*/
@@ -70,6 +74,31 @@ li {
/*----- Global Layout -----*/
#loading {
position: fixed;
left: 50%;
margin: 1.5em -40px;
width: 80px;
padding: 0.2em 0.2em;
text-align: center;
font-weight: bold;
font-size: 13px;
color: #FFFFFF;
border: 1px solid black;
background-color: #7989D6;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
-webkit-box-shadow: 0 2px 8px rgba(0,0,0,0.6);
-moz-box-shadow: 0 2px 8px rgba(0,0,0,0.6);
box-shadow: 0 2px 8px rgba(0,0,0,0.6);
z-index: 2; /* just below #fader */
}
#container {
width: 1150px;
margin: 0 auto;
@@ -426,7 +455,7 @@ li {
}
table {
border-collapse: collapse;
border-collapse: collapse;
}
td {
@@ -448,7 +477,7 @@ td.empty-corner-lt {
.html5-hashbang-example {
height: 255px;
margin-left: -40px;
padding-left: 30px;
padding-left: 30px;
}
.html5-hashbang-example div {
@@ -459,3 +488,7 @@ td.empty-corner-lt {
.html5-hashbang-example div input {
width: 360px;
}
.error {
color: red;
}
+36 -29
View File
@@ -2,53 +2,57 @@ DocsController.$inject = ['$location', '$browser', '$window', '$cookies'];
function DocsController($location, $browser, $window, $cookies) {
window.$root = this.$root;
var self = this,
var scope = this,
OFFLINE_COOKIE_NAME = 'ng-offline',
DOCS_PATH = /^\/(api)|(guide)|(cookbook)|(misc)|(tutorial)/,
INDEX_PATH = /^(\/|\/index[^\.]*.html)$/;
this.$location = $location;
self.versionNumber = angular.version.full;
self.version = angular.version.full + " " + angular.version.codeName;
self.subpage = false;
self.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
scope.$location = $location;
scope.versionNumber = angular.version.full;
scope.version = angular.version.full + " " + angular.version.codeName;
scope.subpage = false;
scope.offlineEnabled = ($cookies[OFFLINE_COOKIE_NAME] == angular.version.full);
scope.futurePartialTitle = null;
scope.loading = 0;
if (!$location.path() || INDEX_PATH.test($location.path())) {
$location.path('/api').replace();
}
this.$watch('$location.path()', function(scope, path) {
scope.$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});
scope.sectionId = parts[1];
scope.partialId = parts[2] || 'index';
scope.pages = angular.Array.filter(NG_PAGES, {section: scope.sectionId});
scope.loading++;
var i = self.pages.length;
var i = scope.pages.length;
while (i--) {
if (self.pages[i].id == self.partialId) {
self.partialTitle = self.pages[i].name;
if (scope.pages[i].id == scope.partialId) {
// TODO(i): this is not ideal but better than updating the title before a partial arrives,
// which results in the old partial being displayed with the new title
scope.futurePartialTitle = scope.pages[i].name;
break;
}
}
if (i<0) {
self.partialTitle = 'Error: Page Not Found!';
delete self.partialId;
scope.partialTitle = 'Error: Page Not Found!';
delete scope.partialId;
}
}
});
this.getUrl = function(page){
scope.getUrl = function(page) {
return page.section + '/' + page.id;
};
this.getCurrentPartial = function(){
scope.getCurrentPartial = function() {
return this.partialId ? ('./partials/' + this.sectionId + '/' + this.partialId + '.html') : '';
};
this.getClass = function(page) {
scope.getClass = function(page) {
var depth = page.depth,
cssClass = 'level-' + depth + (page.name == this.partialId ? ' selected' : '');
@@ -58,28 +62,30 @@ function DocsController($location, $browser, $window, $cookies) {
return cssClass;
};
this.selectedSection = function(section) {
return section == self.sectionId ? 'current' : '';
scope.selectedSection = function(section) {
return section == scope.sectionId ? 'current' : '';
};
this.selectedPartial = function(partial) {
return partial.id == self.partialId ? 'current' : '';
scope.selectedPartial = function(partial) {
return partial.id == scope.partialId ? 'current' : '';
};
this.afterPartialLoaded = function() {
scope.afterPartialLoaded = function() {
scope.loading--;
scope.partialTitle = scope.futurePartialTitle;
SyntaxHighlighter.highlight();
$window.scrollTo(0,0);
$window._gaq.push(['_trackPageview', $location.path()]);
};
this.getFeedbackUrl = function() {
scope.getFeedbackUrl = function() {
return "mailto:angular@googlegroups.com?" +
"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 */
this.enableOffline = function() {
scope.enableOffline = function() {
//The cookie will be good for one year!
var date = new Date();
date.setTime(date.getTime()+(365*24*60*60*1000));
@@ -94,14 +100,15 @@ function DocsController($location, $browser, $window, $cookies) {
// bind escape to hash reset callback
angular.element(window).bind('keydown', function(e) {
if (e.keyCode === 27) {
self.subpage = false;
self.$eval();
scope.$apply(function() {
scope.subpage = false;
});
}
});
}
// prevent compilation of code
angular.widget('code', function(element){
angular.widget('code', function(element) {
element.attr('ng:non-bindable', 'true');
});
+5 -3
View File
@@ -12,8 +12,8 @@
// 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)/) || ['', 'index.html'])[1],
rUrl = /(api|guide|misc|tutorial|cookbook|index[^\.]*\.html).*$/,
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),
@@ -99,7 +99,7 @@
</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>
@@ -111,6 +111,8 @@
</ul>
</div>
<div id="loading" ng:show="loading">Loading...</div>
<div id="content-panel">
<h2 ng:bind="partialTitle"></h2>
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -2,7 +2,6 @@
* All writing related code here. This is so that we can separate the async code from sync code
* for testability
*/
require.paths.push(__dirname);
var qfs = require('q-fs');
var Q = require('qq');
var OUTPUT_DIR = "build/docs/";
@@ -18,7 +17,7 @@ exports.output = function(file, content) {
};
//recursively create directory
exports.makeDir = function (path) {
exports.makeDir = function(path) {
var parts = path.split(/\//);
var path = ".";
//Sequentially create directories
@@ -99,7 +98,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 +127,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 +139,4 @@ exports.toString = function toString(obj) {
};
function noop(){};
function noop() {};
+2 -2
View File
@@ -12,11 +12,11 @@
<span>&lt;angular/&gt; 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>
+3 -3
View File
@@ -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;
});
}
-21
View File
@@ -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.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>
-21
View File
@@ -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.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>
-21
View File
@@ -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.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>
-55
View File
@@ -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>
+1 -1
View File
@@ -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();
+6 -6
View File
@@ -11,7 +11,7 @@
<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=">&lt;&lt; All</a></span>
]
<div class="loading">Loading...</div>
+1 -1
View File
@@ -12,7 +12,7 @@
(TODO: I should fetch current tweets)
<div class="tweeter box">
<h1>Tweets: {{$anchor.user}}</h1>
[ Filter: <input type="text" name="tweetFilter"/> (TODO: this should act as search box)
[ Filter: <input type="text" ng:model="tweetFilter"/> (TODO: this should act as search box)
<span ng:show="$anchor.user">| <a href="#user=">&lt;&lt; All</a></span>
]
<div class="loading">Loading...</div>
+3 -3
View File
@@ -1,7 +1,7 @@
function noop(){}
$(document).ready(function(){
function noop() {}
$(document).ready(function() {
function xhr(method, url, data, callback){
jQuery.getJSON(url, function(){
jQuery.getJSON(url, function() {
callback.apply(this, arguments);
scope.updateView();
});
+8 -8
View File
@@ -36,7 +36,7 @@ if (flag == '--login') {
help();
}
function help(){
function help() {
console.log('Synopsys');
console.log('gdocs.js --login <username>');
console.log('gdocs.js --fetch [<docs collection>]');
@@ -141,7 +141,7 @@ function login(username, password){
);
}
function getAuthToken(){
function getAuthToken() {
var pwdFile = 'tmp/gdocs.auth';
try {
fs.statSync(pwdFile);
@@ -164,10 +164,10 @@ function request(method, url, options, response) {
case 200:
var data = [];
res.setEncoding('utf8');
res.on('end', function (){ response(data.join('')); });
res.on('close', function (){ response(data.join('')); }); // https
res.on('end', function () { response(data.join('')); });
res.on('close', function () { response(data.join('')); }); // https
res.on('data', function (chunk) { data.push(chunk); });
res.on('error', function (e){ console.log(e); });
res.on('error', function (e) { console.log(e); });
break;
case 401:
console.log('Eror: Login credentials expired! Please login.');
@@ -190,7 +190,7 @@ function request(method, url, options, response) {
}
if (options.data)
request.write(encodeData(options.data));
request.on('end', function(){
request.on('end', function() {
console.log('end');
});
request.end();
@@ -212,7 +212,7 @@ function askPassword(callback) {
console.log('Enter your password:');
var password = "";
stdin.on("data", function (c) {
stdin.on("data", function(c) {
c = c + "";
switch (c) {
case "\n": case "\r": case "\u0004":
@@ -236,7 +236,7 @@ function reflow(text, margin) {
text.split(/\n/).forEach(function(line) {
var col = 0;
var reflowLine = '';
function flush(){
function flush() {
reflowLine = reflowLine.replace(/\s*$/, '');
lines.push(reflowLine);
reflowLine = '';
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
if [ ! -e gen_docs.disable ]; then
/usr/bin/env jasmine-node docs/spec -i docs/src -i lib --noColor && node docs/src/gen-docs.js
jasmine-node docs/spec --noColor && node docs/src/gen-docs.js
fi

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