Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd409bd2df | |||
| e80a64883d | |||
| 8b2753eee2 | |||
| 02aa7978d3 | |||
| 5bf7ff5a3e | |||
| a01aa7055c | |||
| a01d888eec | |||
| 28800a48ad | |||
| dea72be0cc | |||
| cd139f5767 | |||
| 10a7521f0b | |||
| 887da5684b | |||
| 95a29d7bde | |||
| 945056b166 | |||
| 128feb2674 | |||
| a709dc19b8 | |||
| 65585a2d3c | |||
| edbe9d8ca8 | |||
| 9e67da420b | |||
| 5fc2b96b97 | |||
| a7ee4a8884 | |||
| eea0de6db4 | |||
| d15165f207 | |||
| c7998f5f99 | |||
| 08e3b1edbb | |||
| fe743e31f8 | |||
| 55ce859998 | |||
| 5a3c9190dc | |||
| e160944bfa | |||
| 6f8940c5d0 | |||
| 87cbf9f591 | |||
| fd6e5e3f31 | |||
| 7d4aee31bb | |||
| 7a54d2791f | |||
| 65243b7d60 | |||
| 9e30baad3f | |||
| a070ff5ad0 | |||
| c90abf057b | |||
| cdc093a463 | |||
| 00cc9eb32a | |||
| ef4bb28be1 | |||
| 496e6bf901 | |||
| 23b255a8b7 | |||
| e2154cbc0b | |||
| 0a5c00abf8 | |||
| a004d487c4 | |||
| 037f30a0c9 | |||
| c37bb2dc28 | |||
| 7cf70c587e | |||
| 1777110958 | |||
| d2089a1633 | |||
| e9ce22592a | |||
| 4f6fe1d479 | |||
| 0f19cd3625 | |||
| c1caf2560a | |||
| b07cc0e392 | |||
| a26f192e01 | |||
| f943180e34 | |||
| e6a6e32c72 | |||
| e1cfb99ae9 | |||
| 1cdc050ce7 | |||
| d600c608e3 | |||
| 86321d1f57 | |||
| 8724e97b7e | |||
| bf8013ad57 | |||
| a6c45c3e66 | |||
| e626f95469 | |||
| 2fab1a2da9 | |||
| f3a39a6418 | |||
| f00b157841 | |||
| ae20f0c1b3 | |||
| 9f6c5db2a6 | |||
| ea9ca651d2 | |||
| d6db4b1749 | |||
| 9798f5e35f | |||
| 94bf24e3b6 | |||
| fbc7f1b454 | |||
| bf7c9d9900 | |||
| 568574b915 | |||
| 46d690ff01 | |||
| 882f412d57 | |||
| 0d4def68ae | |||
| d35c1ac8b0 | |||
| 2a9f7b7287 | |||
| 245b60d69a | |||
| b6a01bd27d | |||
| aaaad298ac | |||
| ba6b68b6ae | |||
| ed768ebc53 | |||
| 9fd3dfe49d | |||
| d7686a429c | |||
| 6c0cf17404 | |||
| c648fee5c2 | |||
| 9462c78fbf | |||
| c4497d60bc | |||
| 5690627766 | |||
| 2389c71238 | |||
| ce7ab3d1ee | |||
| 7db3b54c1f | |||
| 21ad176246 | |||
| df1d222dd0 | |||
| 7eea26323b | |||
| f0fb4a3928 | |||
| 0144fa1ca9 | |||
| b612826158 | |||
| e5a3acacad |
@@ -16,7 +16,7 @@
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>LaunchConfigHandle</key>
|
||||
<value><project>/.externalToolBuilders/JSTD_Tests.launch</value>
|
||||
<value><project>/.externalToolBuilders/docs.launch</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
@@ -26,7 +26,7 @@
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>LaunchConfigHandle</key>
|
||||
<value><project>/.externalToolBuilders/docs.launch</value>
|
||||
<value><project>/.externalToolBuilders/JSTD_Tests.launch</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
|
||||
@@ -1,3 +1,93 @@
|
||||
<a name="0.9.12"><a/>
|
||||
# <angular/> 0.9.12 thought-implanter (2011-03-03) #
|
||||
|
||||
### API
|
||||
- Added a delay parameter to the [$defer] service. (commit edbe9d8c)
|
||||
- Added `scope()` method to [angular.element][element] (jQuery) instances to retrieve a [scope]
|
||||
associated with a given DOM element. (commit 0a5c00ab)
|
||||
- Added inference of DI dependencies from function signature. This feature is experimental, check
|
||||
out [dependency injection][guide.di] docs. (commit 7d4aee31)
|
||||
|
||||
|
||||
### New Features
|
||||
- Angular now correctly recognizes and uses jQuery even if it was loaded after angular's script.
|
||||
More info at [angular.element][element]. (commit a004d487)
|
||||
- All built-in angular services are now lazy-loaded. (commit a070ff5a)
|
||||
- To make styling of custom html tags created via [widgets][widget] and [directives][directive]
|
||||
easier, all of these elements now contain a css class with name in form of
|
||||
`<namespace>-<directive/widget name>`, e.g. `<ng:include class="ng-include">`. (commit c7998f5f)
|
||||
- [$xhr] service now automatically detects and strips google-style JSON security prefix from http
|
||||
responses. (commit cd139f57)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
- Rewrite of JQuery lite implementation for better supports operations on multiple nodes when
|
||||
matched by a selector and remove other bugs. (commit 00cc9eb3)
|
||||
- Corrected an issue where properties inherited from \_\_proto\_\_ show up in ng:repeat.
|
||||
(commit 9e67da42)
|
||||
- Fixed url encoding issue affecting [$resource] service. (commits e9ce2259 + 9e30baad)
|
||||
- Removed `$eval()` call from the [$cookies] factory function, which was causing duplicate
|
||||
instances of singleton services to be created. (commit 65585a2d)
|
||||
|
||||
|
||||
### Docs
|
||||
- New docs [contribution guidelines][contribute].
|
||||
- New [description of release artifacts][downloading].
|
||||
- Lots of improvements and other new content.
|
||||
|
||||
|
||||
### Breaking changes
|
||||
- Removed the `$init()` method that used to be called after compilation of a template. This should
|
||||
affect only fraction of angular apps because the api was primarily being used by low level widgets
|
||||
tests.
|
||||
|
||||
The old way of compiling the DOM element was angular.compile(element).$init(); The $init was there
|
||||
to allow the users to do any work to the scope before the view would be bound. This is a left over
|
||||
from not having proper MVC. The new recommended way to deal with initializing scope is to put it
|
||||
in the root constructor controller. To migrate simply remove the call to $init() and move any code
|
||||
you had before $init() to the root controller.
|
||||
|
||||
(commit 23b255a8)
|
||||
- Changed [angular.compile][compile] API from `angular.compile(element[, scope])` to
|
||||
`angular.compile(element)([scope], [cloneAttachFn])` (commits ef4bb28b + 945056b1)
|
||||
- Removed ng:watch directives since it encourages logic in the UI. (commit 87cbf9f5)
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="0.9.11"><a/>
|
||||
# <angular/> 0.9.11 snow-maker (2011-02-08) #
|
||||
|
||||
### Documentation
|
||||
- completed migration of docs from the wiki site to
|
||||
[http://docs.angularjs.org/](http://docs.angularjs.org/)
|
||||
- many, but by far not all, docs were updated, improved and cleaned up
|
||||
|
||||
### Features
|
||||
- [`$route`](http://docs.angularjs.org/#!angular.service.$route) service now supports these
|
||||
features:
|
||||
- route not found handling via `#otherwise()`
|
||||
- redirection support via `#when('/foo', {redirectTo: '/bar'})` (including param interpolation)
|
||||
- setting the parent scope for scopes created by the service via `#parent()`
|
||||
- reloading the current route via `#reload()`
|
||||
|
||||
### API
|
||||
- added `angular.element(...).scope()` method to retrieve scope for a given element.
|
||||
|
||||
### Bug Fixes
|
||||
- <option> value attribute gets clobbered when the element contains new line character(s).
|
||||
- <ng:view> widget now works when nested inside an <ng:include> widget
|
||||
- other various small fixes
|
||||
|
||||
### Breaking changes
|
||||
- mock [`$browser`](http://docs.angularjs.org/#!angular.mock.service.$browser) now throws an
|
||||
exception if the `flush()` method is called when there are no requests to be flushed. If you
|
||||
experience `No xhr requests to be flushed!` errors in your tests, it's because you called
|
||||
`$browser.xhr.flush()` unexpectedly. To make the error go away, either make sure your code makes a
|
||||
request via the `$xhr` service or remove all unneeded `flush()` calls.
|
||||
|
||||
|
||||
<a name="0.9.10"><a/>
|
||||
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
|
||||
|
||||
### Features
|
||||
@@ -21,6 +111,7 @@ with the `$route` service
|
||||
- lots of improvements related to formatting of the content of docs.anguarjs.org
|
||||
|
||||
|
||||
<a name="0.9.9"><a/>
|
||||
# <angular/> 0.9.9 time-shift (2011-01-13) #
|
||||
|
||||
### Security
|
||||
@@ -73,18 +164,21 @@ with the `$route` service
|
||||
(e.g. `ng:init="$location = $service('$location'), ...`) in the view or more correctly create
|
||||
a service like this:
|
||||
|
||||
angular.service('published-svc-shim', function() {
|
||||
this.$location = this.$service('$location');
|
||||
this.$route = this.$service('$route');
|
||||
this.$cookies = this.$service('$cookies');
|
||||
this.$window = this.$service('$window');
|
||||
this.$document = this.$service('$document');
|
||||
this.$exceptionHandler = this.$service('$exceptionHandler');
|
||||
this.$invalidWidgets = this.$service('$invalidWidgets');
|
||||
}, {$eager: true});
|
||||
angular.service('published-svc-shim', function($location, $route, $cookies, $window,
|
||||
$document, $exceptionHandler, $invalidWidgets) {
|
||||
this.$location = $location;
|
||||
this.$route = $route;
|
||||
this.$cookies = $cookies;
|
||||
this.$window = $window;
|
||||
this.$document = $document;
|
||||
this.$exceptionHandler = $exceptionHandler;
|
||||
this.$invalidWidgets = $invalidWidgets;
|
||||
}, {$inject: ['$location', '$route', '$cookies', '$window', '$document', '$exceptionHandler',
|
||||
'$invalidWidgets'],
|
||||
$eager: true});
|
||||
|
||||
- In the light of the `eager-published` change, to complete the cleanup we renamed `$creation`
|
||||
property of services to `eager` with its value being a boolean.
|
||||
property of services to `$eager` with its value being a boolean.
|
||||
To transition, please rename all `$creation: 'eager'` declarations to `$eager: true`.
|
||||
(commit 1430c6d6)
|
||||
|
||||
@@ -93,6 +187,7 @@ with the `$route` service
|
||||
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
|
||||
|
||||
|
||||
<a name="0.9.8"><a/>
|
||||
# <angular/> 0.9.8 astral-projection (2010-12-23) #
|
||||
|
||||
### Docs/Getting started
|
||||
@@ -106,6 +201,7 @@ with the `$route` service
|
||||
- Ignore input widgets which have no name (issue #153)
|
||||
|
||||
|
||||
<a name="0.9.7"><a/>
|
||||
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
|
||||
|
||||
### Bug Fixes
|
||||
@@ -124,6 +220,7 @@ with the `$route` service
|
||||
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
|
||||
|
||||
|
||||
<a name="0.9.6"><a/>
|
||||
# <angular/> 0.9.6 night-vision (2010-12-06) #
|
||||
|
||||
### Security
|
||||
@@ -153,6 +250,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/>
|
||||
# <angular/> 0.9.5 turkey-blast (2010-11-25) #
|
||||
|
||||
### Docs
|
||||
@@ -162,6 +260,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/>
|
||||
# <angular/> 0.9.4 total-recall (2010-11-18) #
|
||||
|
||||
### Docs
|
||||
@@ -178,6 +277,7 @@ with the `$route` service
|
||||
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
|
||||
|
||||
|
||||
<a name="0.9.3"><a/>
|
||||
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
|
||||
|
||||
### Docs
|
||||
@@ -205,6 +305,7 @@ with the `$route` service
|
||||
simple RegExp validator.
|
||||
|
||||
|
||||
<a name="0.9.2"><a/>
|
||||
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
|
||||
|
||||
### Docs
|
||||
@@ -242,6 +343,7 @@ with the `$route` service
|
||||
implements HEAD
|
||||
|
||||
|
||||
<a name="0.9.1"><a/>
|
||||
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
|
||||
|
||||
### Security
|
||||
@@ -268,6 +370,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/>
|
||||
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
|
||||
|
||||
### Security
|
||||
@@ -297,3 +400,19 @@ with the `$route` service
|
||||
|
||||
### Big Thanks to Our Community Contributors
|
||||
- Vojta Jina
|
||||
|
||||
|
||||
|
||||
|
||||
[scope]: http://docs.angularjs.org/#!angular.scope
|
||||
[compile]: http://docs.angularjs.org/#!angular.compile
|
||||
[element]: http://docs.angularjs.org/#!angular.element
|
||||
[widget]: http://docs.angularjs.org/#!angular.widget
|
||||
[$defer]: http://docs.angularjs.org/#!angular.service.$defer
|
||||
[$cookies]: http://docs.angularjs.org/#!angular.service.$cookies
|
||||
[$xhr]: http://docs.angularjs.org/#!angular.service.$xhr
|
||||
[$resource]: http://docs.angularjs.org/#!angular.service.$resource
|
||||
[directive]: http://docs.angularjs.org/#!angular.directive
|
||||
[guide.di]: http://docs.angularjs.org/#!guide.di
|
||||
[downloading]: http://docs.angularjs.org/#!downloading
|
||||
[contribute]: http://docs.angularjs.org/#!contribute
|
||||
|
||||
@@ -15,7 +15,23 @@ ANGULAR = [
|
||||
'src/filters.js',
|
||||
'src/formatters.js',
|
||||
'src/validators.js',
|
||||
'src/services.js',
|
||||
'src/service/cookieStore.js',
|
||||
'src/service/cookies.js',
|
||||
'src/service/defer.js',
|
||||
'src/service/document.js',
|
||||
'src/service/exceptionHandler.js',
|
||||
'src/service/hover.js',
|
||||
'src/service/invalidWidgets.js',
|
||||
'src/service/location.js',
|
||||
'src/service/log.js',
|
||||
'src/service/resource.js',
|
||||
'src/service/route.js',
|
||||
'src/service/updateView.js',
|
||||
'src/service/window.js',
|
||||
'src/service/xhr.bulk.js',
|
||||
'src/service/xhr.cache.js',
|
||||
'src/service/xhr.error.js',
|
||||
'src/service/xhr.js',
|
||||
'src/directives.js',
|
||||
'src/markups.js',
|
||||
'src/widgets.js',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.attrMarkup
|
||||
|
||||
@description
|
||||
Attribute markup extends the angular compiler in a very similar way as {@link angular.markup} except
|
||||
that it allows you to modify the state of the attribute text rather then the contest of a node.
|
||||
|
||||
<pre>
|
||||
angular.attrMarkup('extraClass', function(attrValue, attrName, element){
|
||||
if (attrName == 'additional-class') {
|
||||
element.addClass(attrValue);
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
@@ -9,16 +9,17 @@ Wraps a raw DOM element or HTML string as [jQuery](http://jquery.com) element.
|
||||
jQuery is loaded or a function that wraps the element or string in angular's jQuery lite
|
||||
implementation.
|
||||
|
||||
Real jQuery always takes precedence if it was loaded before angular.
|
||||
Real jQuery always takes precedence (as long as it was loaded before `DOMContentEvent`)
|
||||
|
||||
Angular's jQuery lite implementation is a tiny API-compatible subset of jQuery which allows
|
||||
angular to manipulate DOM. The functions implemented are usually just the basic versions of
|
||||
them and might not support arguments and invocation styles.
|
||||
angular to manipulate DOM. The jQuery lite implements only a subset of jQuery api, with the
|
||||
focus on the most commonly needed functionality and minimal footprint. For this reason only a
|
||||
limited number of jQuery methods, arguments and invocation styles are supported.
|
||||
|
||||
NOTE: All element references in angular are always wrapped with jQuery (lite) and are never
|
||||
raw DOM references.
|
||||
|
||||
Angular's jQuery lite implements these functions:
|
||||
## Angular's jQuery lite implements these functions:
|
||||
|
||||
- [addClass()](http://api.jquery.com/addClass/)
|
||||
- [after()](http://api.jquery.com/after/)
|
||||
@@ -39,5 +40,10 @@ Angular's jQuery lite implements these functions:
|
||||
- [text()](http://api.jquery.com/text/)
|
||||
- [trigger()](http://api.jquery.com/trigger/)
|
||||
|
||||
## Additionally these methods extend the jQuery and are available in both jQuery and jQuery lite
|
||||
version:
|
||||
|
||||
- `scope()` - retrieves the current angular scope of the element.
|
||||
|
||||
@param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
|
||||
@returns {Object} jQuery object.
|
||||
|
||||
@@ -46,12 +46,13 @@ You can use these variables in the function:
|
||||
the DOM in addition to transforming the input.
|
||||
|
||||
|
||||
@exampleDescription
|
||||
@example
|
||||
The following example filter reverses a text string. In addition, it conditionally makes the
|
||||
text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
|
||||
modification).
|
||||
|
||||
@example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
angular.filter('reverse', function(input, uppercase, color) {
|
||||
var out = "";
|
||||
@@ -73,4 +74,14 @@ You can use these variables in the function:
|
||||
Reverse: {{text|reverse}}<br>
|
||||
Reverse + uppercase: {{text|reverse:true}}<br>
|
||||
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
|
||||
</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');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
|
||||
@@ -37,42 +37,46 @@ angular.formatter('reverse', {
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<script type="text/javascript">
|
||||
function reverse(text) {
|
||||
var reversed = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
reversed.unshift(text.charAt(i));
|
||||
<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('');
|
||||
}
|
||||
return reversed.join('');
|
||||
}
|
||||
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
angular.formatter('reverse', {
|
||||
parse: function(value){
|
||||
return reverse(value||'').toUpperCase();
|
||||
},
|
||||
format: function(value){
|
||||
return reverse(value||'').toLowerCase();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Formatted:
|
||||
<input type="text" name="data" value="angular" ng:format="reverse"/>
|
||||
<br/>
|
||||
Formatted:
|
||||
<input type="text" name="data" value="angular" ng:format="reverse"/>
|
||||
<br/>
|
||||
|
||||
Stored:
|
||||
<input type="text" name="data"/><br/>
|
||||
<pre>{{data}}</pre>
|
||||
Stored:
|
||||
<input type="text" name="data"/><br/>
|
||||
<pre>{{data}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should store reverse', function(){
|
||||
expect(element('.doc-example-live input:first').val()).toEqual('angular');
|
||||
expect(element('.doc-example-live input:last').val()).toEqual('RALUGNA');
|
||||
|
||||
this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
$document.elements('.doc-example-live input:last').val('XYZ').trigger('change');
|
||||
done();
|
||||
});
|
||||
expect(element('.doc-example-live input:first').val()).toEqual('zyx');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
@scenario
|
||||
it('should store reverse', function(){
|
||||
expect(element('.doc-example input:first').val()).toEqual('angular');
|
||||
expect(element('.doc-example input:last').val()).toEqual('RALUGNA');
|
||||
|
||||
this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
$document.elements('.doc-example input:last').val('XYZ').trigger('change');
|
||||
done();
|
||||
});
|
||||
expect(element('.doc-example input:first').val()).toEqual('zyx');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name angular.markup
|
||||
|
||||
@description
|
||||
#Overview
|
||||
Markups allow the angular compiler to transform content of DOM elements or portions of this content
|
||||
into other text or DOM elements for further compilation. Markup extensions do not themselves produce
|
||||
linking functions. Think of markup as a way to produce shorthand for a {@link angular.widget widget}
|
||||
or a {@link angular.directive directive}.
|
||||
|
||||
#`{{}}` (double curly) built-in markup
|
||||
`{{}}` markup is a built-in markup, which translates the enclosed expression into an
|
||||
{@link angular.directive.ng:bind ng:bind} directive. It simply transforms
|
||||
|
||||
<pre>
|
||||
{{expression}}
|
||||
</pre>
|
||||
|
||||
to:
|
||||
|
||||
<pre>
|
||||
<span ng:bind="expression"></span>
|
||||
</pre>
|
||||
|
||||
For example `{{1+2}}` is easier to write and understand than `<span ng:bind="1+2"></span>`. The
|
||||
expanded elements are then {@link guide.compiler compiled} normally.
|
||||
|
||||
# Custom markup
|
||||
Let's say you want to define this shorthand for a horizontal rule: `---` for `<hr/>`.
|
||||
|
||||
In other words, this HTML:
|
||||
<pre>
|
||||
header
|
||||
---
|
||||
footer
|
||||
</pre>
|
||||
|
||||
should translate to:
|
||||
<pre>
|
||||
header
|
||||
<hr/>
|
||||
footer
|
||||
</pre>
|
||||
|
||||
Here's how the angular compiler could be extended to achieve this:
|
||||
<pre>
|
||||
angular.markup('---', function(text, textNode, parentElement) {
|
||||
var compiler = this;
|
||||
var index = text.indexOf('---');
|
||||
if (index > -1) {
|
||||
var before = compiler.text(text.substring(0, index));
|
||||
var hr = compiler.element('hr');
|
||||
var after = compiler.text(text.substring(index + 3));
|
||||
textNode.after(after);
|
||||
textNode.after(hr);
|
||||
textNode.after(before);
|
||||
textNode.remove();
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
Unlike {@link angular.widget widgets} and {@link angular.directive directives}, in which the
|
||||
compiler matches the name of handler function to a DOM element or attribute name, for markup the
|
||||
compiler calls every markup handler for every text node, giving the handler a chance to transform
|
||||
the text. The markup handler needs to find all the matches in the text.
|
||||
@@ -17,22 +17,22 @@ services if needed.
|
||||
|
||||
Like other core angular variables and identifiers, the built-in services always start with `$`.
|
||||
|
||||
* `{@link angular.service.$browser $browser}`
|
||||
* `{@link angular.service.$window $window}`
|
||||
* `{@link angular.service.$document $document}`
|
||||
* `{@link angular.service.$location $location}`
|
||||
* `{@link angular.service.$log $log}`
|
||||
* `{@link angular.service.$exceptionHandler $exceptionHandler}`
|
||||
* `{@link angular.service.$hover $hover}`
|
||||
* `{@link angular.service.$invalidWidgets $invalidWidgets}`
|
||||
* `{@link angular.service.$route $route}`
|
||||
* `{@link angular.service.$xhr $xhr}`
|
||||
* `{@link angular.service.$xhr.error $xhr.error}`
|
||||
* `{@link angular.service.$xhr.bulk $xhr.bulk}`
|
||||
* `{@link angular.service.$xhr.cache $xhr.cache}`
|
||||
* `{@link angular.service.$resource $resource}`
|
||||
* `{@link angular.service.$cookies $cookies}`
|
||||
* `{@link angular.service.$cookieStore $cookieStore}`
|
||||
* {@link angular.service.$browser $browser}
|
||||
* {@link angular.service.$window $window}
|
||||
* {@link angular.service.$document $document}
|
||||
* {@link angular.service.$location $location}
|
||||
* {@link angular.service.$log $log}
|
||||
* {@link angular.service.$exceptionHandler $exceptionHandler}
|
||||
* {@link angular.service.$hover $hover}
|
||||
* {@link angular.service.$invalidWidgets $invalidWidgets}
|
||||
* {@link angular.service.$route $route}
|
||||
* {@link angular.service.$xhr $xhr}
|
||||
* {@link angular.service.$xhr.error $xhr.error}
|
||||
* {@link angular.service.$xhr.bulk $xhr.bulk}
|
||||
* {@link angular.service.$xhr.cache $xhr.cache}
|
||||
* {@link angular.service.$resource $resource}
|
||||
* {@link angular.service.$cookies $cookies}
|
||||
* {@link angular.service.$cookieStore $cookieStore}
|
||||
|
||||
# Writing your own custom services
|
||||
angular provides only set of basic services, so for any nontrivial application it will be necessary
|
||||
@@ -138,29 +138,38 @@ myController.$inject = ['$location', '$log'];
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<script type="text/javascript">
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
angular.service('notify', function(win) {
|
||||
var msgs = [];
|
||||
return function(msg) {
|
||||
msgs.push(msg);
|
||||
if (msgs.length == 3) {
|
||||
win.alert(msgs.join("\n"));
|
||||
msgs = [];
|
||||
}
|
||||
};
|
||||
}, {$inject: ['$window']});
|
||||
|
||||
function myController(notifyService) {
|
||||
this.callNotify = function(msg) {
|
||||
notifyService(msg);
|
||||
};
|
||||
}
|
||||
function myController(notifyService) {
|
||||
this.callNotify = function(msg) {
|
||||
notifyService(msg);
|
||||
};
|
||||
}
|
||||
|
||||
myController.$inject = ['notify'];
|
||||
</script>
|
||||
myController.$inject = ['notify'];
|
||||
</script>
|
||||
|
||||
<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" />
|
||||
<button ng:click="callNotify(message);">NOTIFY</button>
|
||||
</div>
|
||||
<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" />
|
||||
<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');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
@@ -50,24 +50,28 @@ UPS tracking number.
|
||||
default.
|
||||
|
||||
@example
|
||||
<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: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/);
|
||||
});
|
||||
|
||||
@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/);
|
||||
});
|
||||
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>
|
||||
|
||||
@@ -57,17 +57,22 @@ angular.widget('@my:watch', function(expression, compileElement) {
|
||||
</pre>
|
||||
|
||||
@example
|
||||
<script>
|
||||
angular.widget('my:time', function(compileElement){
|
||||
compileElement.css('display', 'block');
|
||||
return function(linkElement){
|
||||
function update(){
|
||||
linkElement.text('Current time is: ' + new Date());
|
||||
setTimeout(update, 1000);
|
||||
}
|
||||
update();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<my:time></my:time>
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.widget('my:time', function(compileElement){
|
||||
compileElement.css('display', 'block');
|
||||
return function(linkElement){
|
||||
function update(){
|
||||
linkElement.text('Current time is: ' + new Date());
|
||||
setTimeout(update, 1000);
|
||||
}
|
||||
update();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<my:time></my:time>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
@ngdoc overview
|
||||
@name Contributing
|
||||
@description
|
||||
|
||||
<a name="H1_1"></a>
|
||||
# Open Source
|
||||
|
||||
`Angular` is an open source project licensed under the {@link
|
||||
http://github.com/angular/angular.js/blob/master/license MIT license}. Your contributions are
|
||||
always welcome. When working with `angular` source base, please follow the guidelines provided on
|
||||
this page.
|
||||
|
||||
* <a href="#H1_2">Contributing to Source Code</a>
|
||||
* <a href="#H1_3">Applying Code Standards</a>
|
||||
* <a href="#H1_4">Checking Out and Building `Angular`</a>
|
||||
* <a href="#H1_5">Submitting Your Changes</a>
|
||||
|
||||
|
||||
|
||||
<a name="H1_2"></a>
|
||||
# Contributing to Source Code
|
||||
|
||||
We'd love for you to contribute to our source code and to make `angular` even better than it is
|
||||
today! Here are the guidelines we'd like you to use:
|
||||
|
||||
* Major changes that you intend to contribute to the project must be discussed first on our {@link
|
||||
https://groups.google.com/forum/?hl=en#!forum/angular mailing list} so that we can better
|
||||
coordinate our efforts, prevent duplication of work, and help you to craft the change so that it
|
||||
is successfully accepted upstream.
|
||||
* Small changes and bug fixes can be crafted and submitted to Github as a <a href="#H1_5">pull
|
||||
request</a>.
|
||||
|
||||
|
||||
|
||||
<a name="H1_3"></a>
|
||||
# Applying Code Standards
|
||||
|
||||
To ensure consistency throughout the source code, keep these rules in mind as you are working:
|
||||
|
||||
* All features or bug fixes must be tested by one or more <a href="#unit-tests">specs</a>.
|
||||
* All public API methods must be documented with ngdoc, an extended version of jsdoc (we added
|
||||
support for markdown and templating via `@ngdoc` tag). To see how we document our APIs, please
|
||||
check out the existing ngdocs.
|
||||
* With the exceptions listed below, we follow the rules contained in {@link
|
||||
http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml Google's JavaScript Style
|
||||
Guide}:
|
||||
|
||||
* Do not use namespaces: Instead, we wrap the entire `angular` code base in an anonymous closure
|
||||
and export our API explicitly rather than implicitly.
|
||||
* Wrap all code at 100 characters.
|
||||
* Instead of complex inheritance hierarchies, we prefer simple objects. We use prototypical
|
||||
inheritance only when absolutely necessary.
|
||||
* We love functions and closures and, whenever possible, prefer them over objects.
|
||||
* To write concise code that can be better minified, internally we use aliases that map to the
|
||||
external API. See our existing code to see what we mean.
|
||||
* We don't go crazy with type annotations for private internal APIs unless it's an internal API
|
||||
that is used throughout `angular`. The best guidance is to do what makes the most sense.
|
||||
|
||||
|
||||
<a name="H1_4"></a>
|
||||
# Checking Out and Building Angular
|
||||
|
||||
The `angular` source code is hosted at {@link http://github.com Github}, which we also use to
|
||||
accept code contributions. Several steps are needed to check out and build `angular`:
|
||||
|
||||
|
||||
## Installation Dependencies
|
||||
|
||||
Before you can build `angular`, you must install or configure the following dependencies on your
|
||||
machine:
|
||||
|
||||
* {@link http://rake.rubyforge.org Rake}: We use Rake as our build system, which is pre-installed
|
||||
on most Macintosh and Linux machines. If that is not true in your case, you can grab it from the
|
||||
Rake website.
|
||||
* {@link nodejs.org Node.js}: We use Node to generate the documentation and to run a development
|
||||
web server. Depending on your system, you can install Node either from source or as a pre-packaged
|
||||
bundle.
|
||||
* Java: The Java runtime is used to run {@link http://code.google.com/p/js-test-driver
|
||||
JsTestDriver} (JSTD), which we use to run our unit test suite. JSTD binaries are part of the
|
||||
`angular` source base, which means there is no need to install or configure it separately.
|
||||
* Git: The {@link http://help.github.com/mac-git-installation Github Guide to Installing Git} is
|
||||
quite a good source for information on Git.
|
||||
|
||||
|
||||
## Creating a Github Account and Forking Angular
|
||||
|
||||
To create a Github account, follow the instructions {@link https://github.com/signup/free here}.
|
||||
Afterwards, go ahead and {@link http://help.github.com/forking fork} the {@link
|
||||
https://github.com/angular/angular.js main angular repository}.
|
||||
|
||||
|
||||
## Building `Angular`
|
||||
|
||||
To build `angular`, you check out the source code and use Rake to generate the non-minified and
|
||||
minified `angular` files:
|
||||
|
||||
1. To clone your Github repository, run:
|
||||
|
||||
git clone git@github.com:<github username>/angular.js.git
|
||||
|
||||
2. To go to the `angular` directory, run:
|
||||
|
||||
cd angular.js
|
||||
|
||||
3. To add the main `angular` repository as an upstream remote to your repository, run:
|
||||
|
||||
git remote add upstream https://github.com/angular/angular.js.git
|
||||
|
||||
4. To build `angular`, run:
|
||||
|
||||
rake package
|
||||
|
||||
The build output can be located under the `build` directory. It consists of the following files and
|
||||
directories:
|
||||
|
||||
* `angular-x.y.z-<git sha>.tgz` — This is the complete tarball, which contains all of the release
|
||||
build artifacts.
|
||||
* `angular.js` — The non-minified `angular` script.
|
||||
* `angular.min.js` — The minified `angular` script.
|
||||
* `angular-scenario.js` — The `angular` End2End test runner.
|
||||
* `angular-ie-compat.js` — The Internet Explorer compatibility patch file.
|
||||
* `docs/` — A directory that contains all of the files needed to run `docs.angularjs.org`.
|
||||
* `docs/index.html` — The main page for the documentation.
|
||||
* `docs/docs-scenario.html` — The End2End test runner for the documentation application.
|
||||
|
||||
|
||||
## Running a Local Development Web Server
|
||||
|
||||
To debug or test code, it is often useful to have a local HTTP server. For this purpose, we have
|
||||
made available a local web server based on Node.js.
|
||||
|
||||
1. To start the web server, run:
|
||||
|
||||
./nodeserver.sh
|
||||
|
||||
2. To access the local server, go to this website:
|
||||
|
||||
http://localhost:8000/
|
||||
|
||||
By default, it serves the contents of the `angular` project directory.
|
||||
|
||||
|
||||
<a name="unit-tests"></a>
|
||||
## Running the Unit Test Suite
|
||||
|
||||
Our unit and integration tests are written with Jasmine and executed with JsTestDriver. To run the
|
||||
tests:
|
||||
|
||||
1. To start the JSTD server, run:
|
||||
|
||||
./server.sh
|
||||
|
||||
2. To capture one or more browsers, go to this website:
|
||||
|
||||
http://localhost:9876/
|
||||
|
||||
3. To trigger a test execution, run:
|
||||
|
||||
./test.sh
|
||||
|
||||
4. To automatically run the test suite each time one or more of the files in the project directory
|
||||
is changed, you can install `watchr` and then run:
|
||||
|
||||
watchr watchr.rb
|
||||
|
||||
5. To view the output of each test run, you can tail this log file:
|
||||
|
||||
./logs/jstd.log
|
||||
|
||||
|
||||
## Running the End2End Test Suite
|
||||
|
||||
To run the End2End test suite:
|
||||
|
||||
1. Start the local web server.
|
||||
2. In a browser, go to:
|
||||
|
||||
http://localhost:8000/build/docs/docs-scenario.html
|
||||
|
||||
The tests are executed automatically.
|
||||
|
||||
|
||||
|
||||
<a name="H1_5"></a>
|
||||
# Submitting Your Changes
|
||||
|
||||
To create and submit a change:
|
||||
|
||||
1. Create a new branch off the master for your changes:
|
||||
|
||||
git branch my-fix-branch
|
||||
|
||||
2. Check out the branch:
|
||||
|
||||
git checkout my-fix-branch
|
||||
|
||||
3. Create your patch, make sure to have plenty of tests (that pass).
|
||||
|
||||
4. Commit your changes:
|
||||
|
||||
git commit -a
|
||||
|
||||
5. Run JavaScript Lint and be sure to address all new warnings and errors:
|
||||
|
||||
rake lint
|
||||
|
||||
6. Push your branch to Github:
|
||||
|
||||
git push origin my-fix-branch
|
||||
|
||||
7. In Github, send a pull request to `angular:master`.
|
||||
|
||||
8. When the patch is reviewed and merged, delete your branch and pull yours — and other — changes
|
||||
from the main (upstream) repository:
|
||||
* To delete the branch in Github, run:
|
||||
|
||||
git push origin :my-fix-branch
|
||||
|
||||
* To check out the master branch, run:
|
||||
|
||||
git checkout master
|
||||
|
||||
* To delete a local branch, run:
|
||||
|
||||
git branch -D my-fix-branch
|
||||
|
||||
* To update your master with the latest upstream version, run:
|
||||
|
||||
git pull --ff upstream master
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
@@ -0,0 +1,63 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Resources - Buzz
|
||||
@description
|
||||
|
||||
External resources are URLs that provide JSON data, which are then rendered with the help of
|
||||
templates. angular has a resource factory that can be used to give names to the URLs and then
|
||||
attach behavior to them. For example you can use the
|
||||
{@link http://code.google.com/apis/buzz/v1/getting_started.html#background-operations| Google Buzz API}
|
||||
to retrieve Buzz activity and comments.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
BuzzController.$inject = ['$resource'];
|
||||
function BuzzController($resource){
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt:'json', callback:'JSON_CALLBACK'},
|
||||
{ get: {method:'JSON', params:{visibility:'@self'}},
|
||||
replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}
|
||||
});
|
||||
}
|
||||
BuzzController.prototype = {
|
||||
fetch: function(){
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
expandReplies: function(activity) {
|
||||
activity.replies =
|
||||
this.Activity.replies({userId:this.userId, activityId:activity.id});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="BuzzController">
|
||||
<input name="userId" value="googlebuzz"/>
|
||||
<button ng:click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div class="buzz" ng:repeat="item in activities.data.items">
|
||||
<h1 style="font-size: 15px;">
|
||||
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href="" ng:click="expandReplies(item)" style="float: right;">
|
||||
Expand replies: {{item.links.replies[0].count}}
|
||||
</a>
|
||||
</h1>
|
||||
{{item.object.content | html}}
|
||||
<div class="reply" ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
||||
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>:
|
||||
{{reply.content | html}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('fetch buzz and expand', function(){
|
||||
element(':button:contains(fetch)').click();
|
||||
expect(repeater('div.buzz').count()).toBeGreaterThan(0);
|
||||
element('.buzz a:contains(Expand replies):first').click();
|
||||
expect(repeater('div.reply').count()).toBeGreaterThan(0);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
@@ -0,0 +1,114 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Deep Linking
|
||||
@description
|
||||
|
||||
Deep linking allows you to encode the state of the application in the URL so that it can be
|
||||
bookmarked and the application can be restored from the URL to the same state.
|
||||
|
||||
While <angular/> does not force you to deal with bookmarks in any particular way, it has services
|
||||
which make the common case described here very easy to implement.
|
||||
|
||||
# Assumptions
|
||||
|
||||
Your application consists of a single HTML page which bootstraps the application. We will refer
|
||||
to this page as the chrome.
|
||||
Your application is divided into several screens (or views) which the user can visit. For example,
|
||||
the home screen, settings screen, details screen, etc. For each of these screens, we would like to
|
||||
assign a URL so that it can be bookmarked and later restored. Each of these screens will be
|
||||
associated with a controller which define the screen's behavior. The most common case is that the
|
||||
screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
|
||||
have multiple partials, but a single partial is the most common construct. This example makes the
|
||||
partial boundary visible using a blue line.
|
||||
|
||||
You can make a routing table which shows which URL maps to which partial view template and which
|
||||
controller.
|
||||
|
||||
# Example
|
||||
|
||||
In this example we have a simple app which consist of two screens:
|
||||
|
||||
* Welcome: url `#` Show the user contact information.
|
||||
* Settings: url `#/settings` Show an edit screen for user contact information.
|
||||
|
||||
|
||||
The two partials are defined in the following URLs:
|
||||
|
||||
* {@link ./static/settings.html}
|
||||
* {@link ./static/welcome.html}
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
AppCntl.$inject = ['$route']
|
||||
function AppCntl($route) {
|
||||
// define routes
|
||||
$route.when("", {template:'./static/welcome.html', controller:WelcomeCntl});
|
||||
$route.when("/settings", {template:'./static/settings.html', controller:SettingsCntl});
|
||||
$route.parent(this);
|
||||
|
||||
// initialize the model to something useful
|
||||
this.person = {
|
||||
name:'anonymous',
|
||||
contacts:[{type:'email', url:'anonymous@example.com'}]
|
||||
};
|
||||
}
|
||||
|
||||
function WelcomeCntl($route){}
|
||||
WelcomeCntl.prototype = {
|
||||
greet: function(){
|
||||
alert("Hello " + this.person.name);
|
||||
}
|
||||
};
|
||||
|
||||
function SettingsCntl(){
|
||||
this.cancel();
|
||||
}
|
||||
SettingsCntl.prototype = {
|
||||
cancel: function(){
|
||||
this.form = angular.copy(this.person);
|
||||
},
|
||||
|
||||
save: function(){
|
||||
angular.copy(this.form, this.person);
|
||||
window.location.hash = "#";
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="AppCntl">
|
||||
<h1>Your App Chrome</h1>
|
||||
[ <a href="#">Welcome</a> | <a href="#/settings">Settings</a> ]
|
||||
<hr/>
|
||||
<span style="background-color: blue; color: white; padding: 3px;">
|
||||
Partial: {{$route.current.template}}
|
||||
</span>
|
||||
<ng:view style="border: 1px solid blue; margin: 0; display:block; padding:1em;"></ng:view>
|
||||
<small>Your app footer </small>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should navigate to URL', function(){
|
||||
element('a:contains(Welcome)').click();
|
||||
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
|
||||
element('a:contains(Settings)').click();
|
||||
input('form.name').enter('yourname');
|
||||
element(':button:contains(Save)').click();
|
||||
element('a:contains(Welcome)').click();
|
||||
expect(element('ng\\:view').text()).toMatch(/Hello yourname/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
|
||||
initialization of the {@link angular.service.$rouet $route} service with the proper URL routes.
|
||||
* The {@link angular.service.$route $route} service then watches the URL and instantiates the
|
||||
appropriate controller when the URL changes.
|
||||
* The {@link angular.widget.ng:view ng:view} widget loads the view when the URL changes. It also
|
||||
sets the view scope to the newly instantiated controller.
|
||||
* Changing the URL is sufficient to change the controller and view. It makes no difference whether
|
||||
the URL is changed programatically or by the user.
|
||||
@@ -0,0 +1,103 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Form
|
||||
@description
|
||||
|
||||
A web application's main purpose is to present and gather data. For this reason angular strives
|
||||
to make both of these operations trivial. This example shows off how you can build a simple form to
|
||||
allow a user to enter data.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function FormController(){
|
||||
this.user = {
|
||||
name: 'John Smith',
|
||||
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
|
||||
contacts:[{type:'phone', value:'1(234) 555-1212'}]
|
||||
};
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="FormController" class="example">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="user.name" ng: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/>
|
||||
|
||||
<label>Phone:</label>
|
||||
[ <a href="" ng:click="user.contacts.$add()">add</a> ]
|
||||
<div ng:repeat="contact in user.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="user.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>user={{user}}</pre>
|
||||
</div>
|
||||
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should show debug', function(){
|
||||
expect(binding('user')).toMatch(/John Smith/);
|
||||
});
|
||||
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(){
|
||||
using('.example').element('a:contains(X)').click();
|
||||
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
|
||||
});
|
||||
|
||||
it('should validate zip', function(){
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
.not().toMatch(/ng-validation-error/)
|
||||
|
||||
using('.example').input('user.address.zip').enter('abc');
|
||||
|
||||
expect(using('.example').element(':input[name=user.address.zip]').attr('className'))
|
||||
.toMatch(/ng-validation-error/)
|
||||
});
|
||||
|
||||
it('should validate state', function(){
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
.not().toMatch(/ng-validation-error/)
|
||||
|
||||
using('.example').input('user.address.state').enter('XXX');
|
||||
|
||||
expect(using('.example').element(':input[name=user.address.state]').attr('className'))
|
||||
.toMatch(/ng-validation-error/)
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* The user data model is initialized {@link angular.ng:controller controller} and is available in
|
||||
the {@link 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 angular.widget.HTML input widgets} simply refer to the model and are auto bound.
|
||||
* The inputs {@link angular.validator 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
|
||||
reflected in the view.
|
||||
@@ -0,0 +1,105 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Advanced Form
|
||||
@description
|
||||
|
||||
Here we extend the basic form example to include common features such as reverting, dirty state
|
||||
detection, and preventing invalid form submission.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
UserForm.$inject = ['$invalidWidgets'];
|
||||
function UserForm($invalidWidgets){
|
||||
this.$invalidWidgets = $invalidWidgets;
|
||||
this.state = /^\w\w$/;
|
||||
this.zip = /^\d\d\d\d\d$/;
|
||||
this.master = {
|
||||
name: 'John Smith',
|
||||
address:{
|
||||
line1: '123 Main St.',
|
||||
city:'Anytown',
|
||||
state:'AA',
|
||||
zip:'12345'
|
||||
},
|
||||
contacts:[
|
||||
{type:'phone', value:'1(234) 555-1212'}
|
||||
]
|
||||
};
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
UserForm.prototype = {
|
||||
cancel: function(){
|
||||
this.form = angular.copy(this.master);
|
||||
},
|
||||
|
||||
save: function(){
|
||||
this.master = this.form;
|
||||
this.cancel();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="UserForm">
|
||||
|
||||
<label>Name:</label><br/>
|
||||
<input type="text" name="form.name" ng:required/> <br/><br/>
|
||||
|
||||
<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>Phone:</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()" disabled="{{master.$equals(form)}}">Cancel</button>
|
||||
<button ng:click="save()" disabled="{{$invalidWidgets.visible() || master.$equals(form)}}">Save</button>
|
||||
|
||||
<hr/>
|
||||
Debug View:
|
||||
<pre>form={{form}}
|
||||
master={{master}}</pre>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
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();
|
||||
input('form.name').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.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');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
#Things to notice
|
||||
|
||||
* Cancel & save buttons are only enabled if the form is dirty -- there is something to cancel or
|
||||
save.
|
||||
* Save button is only enabled if there are no validation errors on the form.
|
||||
* Cancel reverts the form changes back to original state.
|
||||
* Save updates the internal model of the form.
|
||||
* Debug view shows the two models. One presented to the user form and the other being the pristine
|
||||
copy master.
|
||||
@@ -0,0 +1,31 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: Hello World
|
||||
@description
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" name="name" value="World"/>
|
||||
<hr/>
|
||||
Hello {{name}}!
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should change the binding when user enters text', function(){
|
||||
expect(binding('name')).toEqual('World');
|
||||
input('name').enter('angular');
|
||||
expect(binding('name')).toEqual('angular');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Things to notice
|
||||
|
||||
Take a look through the source and note:
|
||||
|
||||
* The script tag that {@link guide.bootstrap bootstraps} the angular environment.
|
||||
* The text {@link angular.widget.HTML 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 angular.scope scope}.
|
||||
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
|
||||
* The concept of {@link guide.data-binding data binding}, which reflects any changes to the
|
||||
input field in the greeting text.
|
||||
@@ -0,0 +1,125 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook: MVC
|
||||
@description
|
||||
|
||||
MVC allows for a clean an testable separation between the behavior (controller) and the view
|
||||
(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
|
||||
view. This makes it very easy for the controller and the view to share the model.
|
||||
|
||||
The model is simply the controller's this. This makes it very easy to test the controller in
|
||||
isolation since one can simply instantiate the controller and test without a view, because there is
|
||||
no connection between the controller and the view.
|
||||
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function TicTacToeCntl(){
|
||||
this.cellStyle= {
|
||||
'height': '20px',
|
||||
'width': '20px',
|
||||
'border': '1px solid black',
|
||||
'text-align': 'center',
|
||||
'vertical-align': 'middle',
|
||||
'cursor': 'pointer'
|
||||
};
|
||||
this.reset();
|
||||
this.$watch('$location.hashSearch.board', this.readUrl);
|
||||
}
|
||||
TicTacToeCntl.prototype = {
|
||||
dropPiece: function(row, col) {
|
||||
if (!this.winner && !this.board[row][col]) {
|
||||
this.board[row][col] = this.nextMove;
|
||||
this.nextMove = this.nextMove == 'X' ? 'O' : 'X';
|
||||
this.setUrl();
|
||||
}
|
||||
},
|
||||
reset: function(){
|
||||
this.board = [
|
||||
['', '', ''],
|
||||
['', '', ''],
|
||||
['', '', '']
|
||||
];
|
||||
this.nextMove = 'X';
|
||||
this.winner = '';
|
||||
this.setUrl();
|
||||
},
|
||||
grade: function(){
|
||||
var b = this.board;
|
||||
this.winner =
|
||||
row(0) || row(1) || row(2) ||
|
||||
col(0) || col(1) || col(2) ||
|
||||
diagonal(-1) || diagonal(1);
|
||||
function row(r) { return same(b[r][0], b[r][1], b[r][2]);}
|
||||
function col(c) { return same(b[0][c], b[1][c], b[2][c]);}
|
||||
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(){
|
||||
var rows = [];
|
||||
angular.forEach(this.board, function(row){
|
||||
rows.push(row.join(','));
|
||||
});
|
||||
this.$location.hashSearch.board = rows.join(';') + '/' + this.nextMove;
|
||||
},
|
||||
readUrl: function(value) {
|
||||
if (value) {
|
||||
value = value.split('/');
|
||||
this.nextMove = value[1];
|
||||
angular.forEach(value[0].split(';'), function(row, i){
|
||||
this.board[i] = row.split(',');
|
||||
}, this);
|
||||
this.grade();
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<h3>Tic-Tac-Toe</h3>
|
||||
<div ng:controller="TicTacToeCntl">
|
||||
Next Player: {{nextMove}}
|
||||
<div class="winner" ng:show="winner">Player {{winner}} has won!</div>
|
||||
<table class="board">
|
||||
<tr ng:repeat="row in board" style="height:15px;">
|
||||
<td ng:repeat="cell in row" ng:style="cellStyle"
|
||||
ng:click="dropPiece($parent.$index, $index)">{{cell}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<button ng:click="reset()">reset board</button>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should play a game', function(){
|
||||
piece(1, 1);
|
||||
expect(binding('nextMove')).toEqual('O');
|
||||
piece(3, 1);
|
||||
expect(binding('nextMove')).toEqual('X');
|
||||
piece(1, 2);
|
||||
piece(3, 2);
|
||||
piece(1, 3);
|
||||
expect(element('.winner').text()).toEqual('Player X has won!');
|
||||
});
|
||||
|
||||
function piece(row, col) {
|
||||
element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
|
||||
}
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
|
||||
# Things to notice
|
||||
|
||||
* The controller is defined in JavaScript and has no reference to the rendering logic.
|
||||
* The controller is instantiated by <angular/> and injected into the view.
|
||||
* The controller can be instantiated in isolation (without a view) and the code will still execute.
|
||||
This makes it very testable.
|
||||
* The HTML view is a projection of the model. In the above example, the model is stored in the
|
||||
board variable.
|
||||
* All of the controller's properties (such as board and nextMove) are available to the view.
|
||||
* Changing the model changes the view.
|
||||
* The view can call any controller function.
|
||||
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
|
||||
hash so the browser's back button will undo game steps. See deep-linking. This example calls
|
||||
{@link angular.Scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
|
||||
@@ -0,0 +1,60 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Cookbook
|
||||
@description
|
||||
|
||||
Welcome to the angular cookbook. Here we will show you typical uses of angular by example.
|
||||
|
||||
|
||||
# Hello World
|
||||
|
||||
{@link cookbook.helloworld Hello World}: The simplest possible application that demonstrates the
|
||||
classic Hello World!
|
||||
|
||||
|
||||
# Basic Form
|
||||
|
||||
{@link cookbook.form Basic Form}: Displaying forms to the user for editing is the bread and butter
|
||||
of web applications. Angular makes forms easy through bidirectional data binding.
|
||||
|
||||
|
||||
# Advanced Form
|
||||
|
||||
{@link cookbook.formadvanced Advanced Form}: Taking the form example to the next level and
|
||||
providing advanced features such as dirty detection, form reverting and submit disabling if
|
||||
validation errors exist.
|
||||
|
||||
|
||||
# Model View Controller
|
||||
|
||||
{@link cookbook.mvc MVC}: Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
|
||||
to separate the behavior (JavaScript controller) from the presentation (HTML view). This
|
||||
separation aids in maintainability and testability of your project.
|
||||
|
||||
|
||||
# Multi-page App and Deep Linking
|
||||
|
||||
{@link cookbook.deeplinking Deep Linking}: An AJAX application never navigates away from the
|
||||
first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
|
||||
is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
|
||||
prevent you from emailing links to a particular screen within your application.
|
||||
|
||||
Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
|
||||
allowing you to send links to specific screens in your app.
|
||||
|
||||
|
||||
# Services
|
||||
|
||||
{@link angular.service Services}: Services are long lived objects in your applications that are
|
||||
available across controllers. A collection of useful services are pre-bundled with angular but you
|
||||
will likely add your own. Services are initialized using dependency injection, which resolves the
|
||||
order of initialization. This safeguards you from the perils of global state (a common way to
|
||||
implement long lived objects).
|
||||
|
||||
|
||||
# External Resources
|
||||
|
||||
{@link cookbook.buzz Resources}: Web applications must be able to communicate with the external
|
||||
services to get and update data. Resources are the abstractions of external URLs which are
|
||||
specially tailored to angular data binding.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Downloading
|
||||
@description
|
||||
|
||||
# Including angular scripts from code.angularjs.org
|
||||
|
||||
To get started quickly you without worrying about downloading anything and maintaining a local copy,
|
||||
you can point your html `script` tag directly to <http://code.angularjs.org/> urls.
|
||||
|
||||
There are two kinds of urls you care about:
|
||||
|
||||
* http://code.angularjs.org/angular-<version>.js
|
||||
* http://code.angularjs.org/angular-<version>.min.js
|
||||
|
||||
The first one is non-minified version, suitable for web development. The latter one is minified
|
||||
version, which we strongly suggest you use in production.
|
||||
|
||||
To point your code to let's say angular version 0.9.12, use the following template:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Angular App</title>
|
||||
<script src="http://code.angularjs.org/angular-0.9.12.js" ng:autobind></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
|
||||
# Downloading and hosting the files on your own
|
||||
|
||||
This options is for those who want to work with angular offline, or want to host the angular files
|
||||
on their own servers.
|
||||
|
||||
If you navigate to <http://code.angularjs.org/>, you'll see a directory listing with all angular
|
||||
versions since we started releasing versioned build artifacts (quite late in the project lifetime).
|
||||
|
||||
Each directory contains all artifacts that we released for a particular version. Once you navigate
|
||||
to one of these directories you'll see the following list of files:
|
||||
|
||||
* `angular-<version>.js` - This file is non-obfuscated, non-minified, and human-readable by opening
|
||||
it it any editor or browser. In order to get better error messages during development, you should
|
||||
always use this non-minified angular script.
|
||||
|
||||
* `angular-<version>.min.js` - This is a minified and obfuscated version of
|
||||
`angular-<version>.js` created with Closure compiler. Use this version for production in order to
|
||||
minimize the size of the application that is downloaded by your user's browser.
|
||||
|
||||
* `angular-<version>.tgz` - This is a tarball archive which contains all the other files released
|
||||
for this angular version. Use this file to get everything in a single download.
|
||||
|
||||
* `angular-ie-compat-<version>.js` - This is a special file that contains code and data specifically
|
||||
tailored for getting Internet Explorer to work with angular. If you host your own copy of angular
|
||||
files, make sure that this file is available for download and resides under the same parent path as
|
||||
`angular-<version>.js` or `angular-<version>.min.js`.
|
||||
|
||||
* `angular-mocks-<version>.js` - This file contains implementation of mocks that we provide to you
|
||||
to make testing angular apps even easier. Your unit/integration test harness should load this file
|
||||
after `angular-<version>.js` is loaded.
|
||||
|
||||
* `angular-scenario-<version>.js` - This file is a very nifty JavaScript file, which allows you to
|
||||
write and execute end to end tests for angular applications.
|
||||
|
||||
* `docs-<version>` - this directory contains all the files that compose the
|
||||
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
|
||||
our docs, or even more importantly, view the docs offline!
|
||||
@@ -0,0 +1,81 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name FAQ
|
||||
@description
|
||||
|
||||
#FAQ
|
||||
|
||||
### Why is this project called "angular"? Why is the namespace called "ng"?
|
||||
|
||||
Because HTML has angular brackets and "ng" sounds like "angular".
|
||||
|
||||
### Is <angular/> an HTML5 tag?
|
||||
|
||||
No, <angular/> is not an HTML5 tag. angular is an orthogonal project to HTML5; you can use the two
|
||||
together.
|
||||
|
||||
### Is angular a {library, framework, DOM manipulation library, widget library, native plugin}?
|
||||
|
||||
No, angular is none of these. You don't call its functions, it does not call your functions,
|
||||
it does not provide a way to manipulate DOM, but does provide primitives to create UI projections
|
||||
of your data. There are lots of existing widget libraries which you can integrate with angular.
|
||||
It is 100% JavaScript, 100% client side and compatible with both desktop and mobile browsers.
|
||||
|
||||
### Do I need to worry about security holes in angular?
|
||||
|
||||
Like with any technology, angular is not impervious to attack. angular does, however, provide
|
||||
built-in protection from basic security holes including cross-site scripting and HTML injection
|
||||
attacks. angular does round-trip escaping on all strings for you.
|
||||
|
||||
### Can I download the source, build, and host the angular environment locally?
|
||||
|
||||
Yes. See instructions in {@link guide.downloading downloading}.
|
||||
|
||||
### Is angular a templating system?
|
||||
|
||||
At the highest level, angular does look like a just another templating system. But there is one
|
||||
important reason why angular templating system is different and makes it very good fit for
|
||||
application development: bidirectional data binding. The template is compiled on the browser and
|
||||
the compilation step produces a live view. This means you, the developer, don't need to write
|
||||
code to constantly sync the view with the model and the model with the view as in other
|
||||
templating systems.
|
||||
|
||||
### What browsers does angular work with?
|
||||
|
||||
Webkit-based browsers (Safari, Chrome, iPhone, Android, WebOS, BlackBerry 6), Firefox, IE6 and
|
||||
above. Note that CSS only works on IE7 and above.
|
||||
|
||||
### What's angular's performance like?
|
||||
|
||||
angular takes ~300ms to load, render, and compile. In Chrome it uses about 2-5MB of memory. Your
|
||||
app's performance will vary depending on how many bindings you use.
|
||||
|
||||
### How big is the angular bootstrap JS file that I need to include?
|
||||
|
||||
The size of the library itself is < 50KB compressed and obfuscated.
|
||||
|
||||
### Can I use the open-source Closure Library with angular?
|
||||
|
||||
Yes, you can use widgets from the {@link http://code.google.com/closure/library Closure Library}
|
||||
in angular.
|
||||
|
||||
### Does angular use the jQuery library?
|
||||
|
||||
Yes, angular uses {@link http://jquery.com/ jQuery}, the open source DOM manipulation library.
|
||||
If jQuery is not present in your script path, angular falls back on its own implementation of
|
||||
{@link angular.element jQuery lite}. If jQuery is present in the path, angular uses it to
|
||||
manipulate the DOM.
|
||||
|
||||
### What is testability like in angular?
|
||||
|
||||
Very testable. It has an integrated dependency injection framework. See
|
||||
{@link angular.service service} for details.
|
||||
|
||||
### How can I learn more about angular?
|
||||
|
||||
Watch the July 28, 2010 talk
|
||||
"{@link http://www.youtube.com/watch?v=elvcgVSynRg| Angular: A Radically Different Way of Building AJAX Apps}".
|
||||
|
||||
### How is angular licensed?
|
||||
|
||||
The MIT License.
|
||||
@@ -50,7 +50,7 @@ equivalent to the code in the previous section.
|
||||
(function(window, previousOnLoad){
|
||||
window.onload = function(){
|
||||
try { (previousOnLoad||angular.noop)(); } catch(e) {}
|
||||
angular.compile(window.document).$init();
|
||||
angular.compile(window.document)();
|
||||
};
|
||||
})(window, window.onload);
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: CSS
|
||||
@description
|
||||
|
||||
# CSS
|
||||
angular includes built-in CSS classes, which in turn have predefined CSS styles.
|
||||
|
||||
# Built-in CSS classes
|
||||
|
||||
## `ng-exception`
|
||||
|
||||
**Usage:** angular applies this class to a DOM element if that element contains an Expression that
|
||||
threw an exception when evaluated.
|
||||
|
||||
**Styling:** The built-in styling of the ng-exception class displays an error message surrounded
|
||||
by a solid red border, for example:
|
||||
|
||||
> <div class="ng-exception">Error message</div>
|
||||
|
||||
|
||||
|
||||
You can try to evaluate malformed expressions in {@link angualr.expression expression} 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 angular.widget.@ng:validate ng:validate example}.
|
||||
|
||||
## How to override the styles for built-in classes
|
||||
|
||||
To override the styles for these built-in 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>
|
||||
@@ -0,0 +1,304 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Dependency Injection
|
||||
@description
|
||||
Dependency injection (DI) is one of the core design patterns in angular and angular applications. DI
|
||||
allows you to replace almost any part of angular framework or angular application with a custom
|
||||
implementation, allowing for a highly flexible, maintainable and testable code-base.
|
||||
|
||||
Dependency injection is a very common pattern in Java and other statically typed languages. While
|
||||
undervalued among JavaScript developers, we feel strongly that DI in JavaScript allows us to achieve
|
||||
the same benefits as in other languages.
|
||||
|
||||
This document will focus on using dependency injection in angular. It is outside of the scope of
|
||||
this document to explain details of dependency injection. For more information on this topic, please
|
||||
refer to these links:
|
||||
|
||||
* {@link http://en.wikipedia.org/wiki/Dependency_injection DI - Wikipedia}
|
||||
* {@link http://martinfowler.com/articles/injection.html Inversion of Control by Martin Fowler}
|
||||
* Java
|
||||
* {@link http://code.google.com/p/google-guice/ Guice}
|
||||
* {@link http://www.devshed.com/c/a/Java/The-Spring-Framework-Understanding-IoC/ Spring}
|
||||
* {@link http://picocontainer.org/injection.html picoContainer}
|
||||
* .NET
|
||||
* {@link http://msdn.microsoft.com/en-us/magazine/cc163739.aspx MSDN Design Patterns - Dependency Inject}
|
||||
* {@link http://www.springframework.net/ Spring.NET}
|
||||
|
||||
|
||||
|
||||
# Dependency Injection in angular
|
||||
|
||||
Angular's dependency injection story begins with a `service`. Service in angular lingo is a
|
||||
JavaScript object, function, or value that is created by angular's injector via a provided factory
|
||||
function. The factory function is registered with angular via {@link angular.service}.
|
||||
|
||||
<pre>
|
||||
// register a factory for a uniqueId service.
|
||||
angular.service('uniqueId', function(){
|
||||
// calling the factory function creates the instance function
|
||||
var id = 0;
|
||||
return function(){
|
||||
// calling the counter instance function will return and increment the count
|
||||
return ++id;
|
||||
}
|
||||
});
|
||||
</pre>
|
||||
|
||||
At run-time we can access the `uniqueId` service by looking it up with the service locator like
|
||||
this:
|
||||
|
||||
<pre>
|
||||
// create new root scope which has the injector function `$service()`
|
||||
var scope = angular.scope();
|
||||
|
||||
// use the `$service` function to look up the service instance function
|
||||
var idGenerator = scope.$service('uniqueId');
|
||||
expect(idGenerator()).toBe(1);
|
||||
|
||||
// subsequent lookups using the same root scope return the service instance
|
||||
var idGenerator2 = scope.$service('uniqueId');
|
||||
expect(idGenerator).toBe(idGenerator2);
|
||||
|
||||
// since it is same instance calling idGenerator2 returns 2;
|
||||
expect(idGenerator2()).toBe(2);
|
||||
</pre>
|
||||
|
||||
The {@link angular.service service} registry seems like a lot of work, so what are the benefits? To
|
||||
answer this question, it’s important to realize that in large scale applications there are a lot of
|
||||
services which are often dependent on each other, as in this example:
|
||||
|
||||
<pre>
|
||||
angular.service('gadgetFactory', function(uniqueId){
|
||||
return function(){
|
||||
return {gadgetId: uniqueId()};
|
||||
};
|
||||
}, {$inject: ['uniqueId']});
|
||||
</pre>
|
||||
|
||||
Specifically, notice that the `gadgetFactory` takes `uniqueId` service in its arguments. It also
|
||||
declares this dependency with the `$inject` property. There are several benefits to this approach:
|
||||
|
||||
* There is no need for a `main` method for an application responsible for instantiating and wiring
|
||||
these services. The order of service instantiation and wiring can be inferred by examining the
|
||||
`$inject` annotations.
|
||||
* It is easy to replace any one service with a different implementation without having to track down
|
||||
all of the dependencies. This is useful in:
|
||||
* Tests: when mocks of services are needed (for example using mock {@link angular.service.$xhr}.)
|
||||
* Customization: when the service bundled with angular does not do exactly what the application
|
||||
requires.
|
||||
|
||||
More importantly, as we'll soon learn, controllers and other components of angular applications can
|
||||
also declare their dependencies on services and these will be provided without explicitly looking
|
||||
them up, but let's not get ahead of ourselves.
|
||||
|
||||
Lastly, it is important to realize that all angular services are singletons – application singletons
|
||||
to be more precise. This means that there is only one instance of a given service per injector. And
|
||||
since angular is lethally allergic to the global state, it's absolutely possible to create multiple
|
||||
injectors each with its own instance of a given service (but that is not typically needed, except in
|
||||
tests where this property is crucially important).
|
||||
|
||||
|
||||
## Service Locator and Scope
|
||||
|
||||
The {@link angular.injector injector} is responsible for resolving the service dependencies in the
|
||||
application. It gets created and configured with the creation of a root scope in your application.
|
||||
The injector is responsible for caching the instances of services, but this cache is bound to the
|
||||
scope. This means that different root scopes will have different instances of the injector. While
|
||||
typical angular applications will only have one root scope (and hence the services will act like
|
||||
application singletons), in tests it is important to not share singletons across test invocations
|
||||
for isolation reasons. We get this isolation by having each test create its own separate root scope.
|
||||
|
||||
<pre>
|
||||
// crate a root scope
|
||||
var rootScope = angular.scope();
|
||||
// accesss the service loctor
|
||||
var myService = rootScope.$service('myService');
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
# Dependency Injection in Controllers
|
||||
|
||||
So far we have been talking about injector as a service locator. This is because we have been
|
||||
explicitly calling the `$service` method to gain access to the service. Service locator is not
|
||||
dependency injection since the caller is still responsible for retrieving the dependencies. *True
|
||||
dependency injection is like Chuck Norris. Chuck does not ask for dependencies; he declares them.*
|
||||
|
||||
The most common place to use dependency injection in angular applications is in
|
||||
{@link angular.ng:controller controllers}. Here’s a simple example:
|
||||
|
||||
<pre>
|
||||
function MyController($route){
|
||||
// configure the route service
|
||||
$route.when(...);
|
||||
}
|
||||
MyController.$inject = ['$route'];
|
||||
</pre>
|
||||
|
||||
In this example, the `MyController` constructor function takes one argument, the
|
||||
(@link angular.service.$route $route) service. Angular is then responsible for supplying the
|
||||
instance of `$route` to the controller when the constructor is instantiated. There are two ways to
|
||||
cause controller instantiation – by configuring routes with the $route service or by referencing the
|
||||
controller from the HTML template, such as:
|
||||
|
||||
<pre>
|
||||
<!doctype html>
|
||||
<html xmlns:ng="http://angularjs.org" ng:controller="MyController">
|
||||
<script src="http://code.angularjs.org/angular.min.js" ng:autobind></script>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
When angular is instantiating your controller, it needs to know what services, if any, should be
|
||||
injected (passed in as arguments) into the controller. Since there is no reflection in JavaScript,
|
||||
we have to supply this information to angular in the form of an additional property on the
|
||||
controller constructor function called `$inject`. Think of it as annotations for JavaScript.
|
||||
|
||||
<pre>
|
||||
MyController.$inject = ['$route'];
|
||||
</pre>
|
||||
|
||||
The information in `$inject` is then used by the {@link angular.injector injector} to call the
|
||||
function with the correct arguments.
|
||||
|
||||
|
||||
|
||||
# Using Dependency Injection pragmatically
|
||||
|
||||
At times you’ll need to use dependency injection pragmatically, usually when instantiating
|
||||
controllers manually or writing unit tests. This section explains how to go about it.
|
||||
|
||||
## Retrieving Services
|
||||
|
||||
The simplest form of dependency injection is manual retrieval of scopes, known as service locator.
|
||||
We say manual because we are asking the injector for an instance of the service (rather then having
|
||||
the injector provide them to the function). This should be rare since most of the time the dependent
|
||||
services should be injected into the controller using the `$inject` property array.
|
||||
|
||||
<pre>
|
||||
// create a root scope. The root scope will automatically have
|
||||
// `$service` method defined which is configured with all services.
|
||||
// Each instance of root scope will have separate instances of services.
|
||||
var rootScope = angular.scope();
|
||||
|
||||
// ask for a service explicitly
|
||||
var $window = rootScope.$service('$window');
|
||||
</pre>
|
||||
|
||||
|
||||
## Creating Controllers using Dependency Injection
|
||||
|
||||
In a typical angular application the dependency injection is most commonly used when creating
|
||||
controllers.
|
||||
<pre>
|
||||
// declare our own service by registering a factory function.
|
||||
angular.service('counter', function(){
|
||||
var count = 0;
|
||||
return function(){ return count++; };
|
||||
});
|
||||
|
||||
// example of a controller which depends on '$window' and 'counter' service
|
||||
// notice that there is an extra unbound parameter 'name' which will not
|
||||
// be injected and must be supplied by the caller.
|
||||
function MyController($window, counter, name) {
|
||||
}
|
||||
|
||||
// we must declare the dependencies explicitly and in the same order as in
|
||||
// the constructor function. This information is used by the dependency
|
||||
// injection to supply the arguments.
|
||||
// Notice the lack of 'name' argument which makes it an unbound argument.
|
||||
MyController.$inject = ['$window', 'counter'];
|
||||
|
||||
|
||||
// Create a root scope which creates the the injector
|
||||
var rootScope = angular.scope();
|
||||
|
||||
// use the '$new()' method instead of standard 'new' keyword operator to
|
||||
// create an instance of MyController and have the dependency injection
|
||||
// supply the arguments to the controller. The dependency injection only
|
||||
// supplies the bound arguments in `$inject` all addition arguments are
|
||||
// curried from the '$new', in our case 'Alexandria' is the argument which
|
||||
// will be curried to the 'name' argument, while '$window' and 'counter'
|
||||
// are supplied by the dependency injection.
|
||||
var myController = rootScope.$new(MyController, 'Alexandria');
|
||||
// NOTE: the returning controller will be a child scope of parent scope,
|
||||
// in this case the root scope.
|
||||
</pre>
|
||||
|
||||
|
||||
## Calling functions and Curring of arguments
|
||||
|
||||
NOTE: this section is quite lame. The concept it is trying to describe is more closely related to
|
||||
scope#new than scope#$service. We need a better example to discuss here. Ideally a parent controller
|
||||
creating a child controller imperatively via $new where the child controller's constructor function
|
||||
declares a portion of its dependencies via $inject property, but another portion is supplied by the
|
||||
caller of $new (e.g. parentCtrl.$new(ChildCtrl, configParam1, configParam2);
|
||||
|
||||
Finally, you may need to call functions but have the `$inject` properties of the function be
|
||||
supplied by the injector.
|
||||
|
||||
<pre>
|
||||
// create a root scope with the `$service` injector.
|
||||
var rootScope = angular.scope();
|
||||
|
||||
// given a function such as
|
||||
function greet ($window, name) {
|
||||
$window.alert(this.salutation + ' ' + name);
|
||||
}
|
||||
greet.$inject = ['$window'];
|
||||
|
||||
// you can call function 'greet' such that the injector supplies the
|
||||
// '$window' and the caller supplies the function 'this' and the 'name'
|
||||
// argument.
|
||||
var fnThis = {salutation: 'Hello'}
|
||||
rootScope.$service(greet, fnThis, 'world');
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
# Inferring `$inject`
|
||||
|
||||
** EXPERIMENTAL: this is an experimental feature, see the important note at the end of this section
|
||||
for drawbacks. **
|
||||
|
||||
We resort to `$inject` and our own annotation because there is no way in JavaScript to get a list of
|
||||
arguments. Or is there? It turns out that calling `.toString()` on a function returns the function
|
||||
declaration along with the argument names as shown below:
|
||||
|
||||
<pre>
|
||||
function myFn(a,b){}
|
||||
expect(myFn.toString()).toEqual('function myFn(a,b){}');
|
||||
</pre>
|
||||
|
||||
This means that angular can infer the function names after all and use that information to generate
|
||||
the `$inject` annotation automatically. Therefore the following two function definitions are
|
||||
equivalent:
|
||||
|
||||
<pre>
|
||||
// given a user defined service
|
||||
angular.service('serviceA', ...);
|
||||
|
||||
// inject '$window', 'serviceA', curry 'name';
|
||||
function fnA($window, serviceA, name){};
|
||||
fnA.$inject = ['$window', 'serviceA'];
|
||||
|
||||
// inject '$window', 'serviceA', curry 'name';
|
||||
function fnB($window, serviceA_, name){};
|
||||
// implies: fnA.$inject = ['$window', 'serviceA'];
|
||||
</pre>
|
||||
|
||||
If angular does not find an `$inject` annotation on the function, then it calls the `.toString()`
|
||||
and tries to infer what should be injected using the following rules:
|
||||
|
||||
* any argument starting with `$` is angular service and will be added to `$inject` property array.
|
||||
* any argument ending with `_` will be added to the `$inject` property array but we strip the `_`
|
||||
* all arguments following an argument which has neither `$` nor `_` , must not have `$` nor `_`
|
||||
(these are free arguments for {@link http://en.wikipedia.org/wiki/Currying curring})
|
||||
|
||||
**IMPORTANT**
|
||||
Minifiers/obfuscators change the names of function arguments and will therefore break the `$inject`
|
||||
inference. For this reason, either explicitly declare the `$inject` or do not use
|
||||
minifiers/obfuscators. In the future, we may provide a pre-processor which will scan the source code
|
||||
and insert the `$inject` into the source code so that it can be minified/obfuscated.
|
||||
@@ -0,0 +1,207 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Expression
|
||||
@description
|
||||
|
||||
# Expressions
|
||||
Expressions are the bindings that you write in HTML and embed in templates in order to create
|
||||
views in angular. They are not equivalent to JavaScript expressions.
|
||||
|
||||
For example, these are all valid expressions in angular:
|
||||
|
||||
* `1+2={{1+2}}`
|
||||
* `3*10|currency`
|
||||
* `Hello {{name}}!`
|
||||
* `Hello {{'World'}}!`
|
||||
|
||||
|
||||
# angular expressions vs. JS expressions
|
||||
It might be tempting to think of angular view expressions as JavaScript expressions, but that is
|
||||
not entirely correct. angular does not use a simple JavaScript eval of the expression text. You
|
||||
can think of angular expressions as JavaScript expressions with these differences:
|
||||
|
||||
* **Attribute Evaluation:** evaluation of all attributes are against the current scope, not to
|
||||
the global window as in JavaScript.
|
||||
* **Forgiving:** expression evaluation is forgiving to undefined and null, unlike in JavaScript.
|
||||
* **No Control Flow Statements:** you cannot do the following from an angular expression:
|
||||
conditionals, loops, or throw.
|
||||
* **Type Augmentation:** the scope expression evaluator augments built-in types.
|
||||
* **Filters:** you can add filters to an expression, for example to convert raw data into a
|
||||
human-readable format.
|
||||
* **The $:** angular reserves this prefix to differentiate its API names from others.
|
||||
|
||||
If you want to run arbitrary JavaScript code, make it a controller method and call that. If you
|
||||
want to eval an angular expression from JavaScript, use the Scope:$eval() method.
|
||||
|
||||
## Example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
1+2={{1+2}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function(){
|
||||
expect(binding('1+2')).toEqual('3');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
You can try evaluating different expressions here:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="exprs=[]" class="expressions">
|
||||
Expression:
|
||||
<input type='text' name="expr" value="3*10|currency" size="80"/>
|
||||
<button ng:click="exprs.$add(expr)">Evaluate</button>
|
||||
<ul>
|
||||
<li ng:repeat="expr in exprs">
|
||||
[ <a href="" ng:click="exprs.$remove(expr)">X</a> ]
|
||||
<tt>{{expr}}</tt> => <span ng:bind="$parent.$eval(expr)"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should allow user expression testing', function(){
|
||||
element('.expressions :button').click();
|
||||
var li = using('.expressions ul').repeater('li');
|
||||
expect(li.count()).toBe(1);
|
||||
expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
# Attribute Evaluation
|
||||
|
||||
Evaluation of all attributes are against the current scope. Unlike JavaScript, where names
|
||||
default to global window properties, angular expressions have to use $window to refer to the
|
||||
global object. E.g. if you want to call alert(), which is defined on window, an expression must
|
||||
use $window.alert(). This is done intentionally to prevent accidental access to 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>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate expression in binding', function(){
|
||||
var alertText;
|
||||
this.addFutureAction('set mock', function($window, $document, done) {
|
||||
$window.mockWindow = {
|
||||
alert: function(text){ alertText = text; }
|
||||
};
|
||||
done();
|
||||
});
|
||||
element(':button:contains(Greet)').click();
|
||||
expect(this.addFuture('alert text', function(done) {
|
||||
done(null, alertText);
|
||||
})).toBe('Hello World');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
## Forgiving
|
||||
|
||||
Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating a.b.c throws
|
||||
an exception if a is not an object. While this makes sense for a general purpose language, the
|
||||
expression evaluations are primarily used for data binding, which often look like this: `{{a.b.c}}`.
|
||||
It makes more sense to show nothing than to throw an exception if a is undefined (e.g. perhaps
|
||||
we are waiting for the server response, and it will become defined soon). If expression
|
||||
evaluation wasn't forgiving we'd have to write bindings that clutter the code, for example:
|
||||
`{{((a||{}).b||{}).c}}`
|
||||
|
||||
Similarly, invoking a function a.b.c() on undefined or null simply returns undefined.
|
||||
|
||||
Assignments work the same way in reverse. a.b.c = 10 creates the intermediary objects even if a
|
||||
is undefined.
|
||||
|
||||
|
||||
## No Control Flow Statements
|
||||
|
||||
You cannot write a control flow statement in an expression. The reason behind this is core to
|
||||
the angular philosophy that application logic should be in controllers, not in the view. If you
|
||||
need a conditional (including ternary operators), loop, or to throw from a view expression,
|
||||
delegate to a JavaScript method instead.
|
||||
|
||||
|
||||
## Type Augmentation
|
||||
|
||||
Built-in types have methods like [].push(), but the richness of these methods is limited. Consider
|
||||
the example below, which allows you to do a simple search over a canned set of contacts. The
|
||||
example would be much more complicated if we did not have the Array:$filter(). There is no
|
||||
built-in method on Array called $filter and angular doesn't add it to Array.prototype because that
|
||||
could collide with other JavaScript frameworks.
|
||||
|
||||
For this reason the scope expression evaluator augments the built-in types to make them act like
|
||||
they have extra methods. The actual method for $filter() is angular.Array.filter(). You can call
|
||||
it from JavaScript.
|
||||
|
||||
Extensions: You can further extend the expression vocabulary by adding new methods to
|
||||
`angular.Array` or `angular.String`, etc.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="friends = [
|
||||
{name:'John', phone:'555-1212'},
|
||||
{name:'Mary', phone:'555-9876'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
Search: <input name="searchText"/>
|
||||
<table class="example3">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(searchText)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
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');
|
||||
expect(tr.count()).toBe(2);
|
||||
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
||||
## Filters
|
||||
|
||||
When presenting data to the user, you might need to convert the data from its raw format to a
|
||||
user-friendly format. For example, you might have a data object that needs to be formatted
|
||||
according to the locale before displaying it to the user. You can pass expressions through a
|
||||
chain of filters like this:
|
||||
|
||||
<pre>
|
||||
name | uppercase
|
||||
</pre>
|
||||
|
||||
The expression evaluator simply passes the value of name to angular.filter.uppercase.
|
||||
|
||||
Chain filters using this syntax:
|
||||
|
||||
<pre>
|
||||
value | filter1 | filter2
|
||||
</pre>
|
||||
|
||||
You can also pass colon-delimited arguments to filters, for example, to display the number 123
|
||||
with 2 decimal points: 123 | number:2
|
||||
|
||||
# The $
|
||||
|
||||
You might be wondering, what is the significance of the $ prefix? It is simply a prefix that
|
||||
angular chooses to differentiate its API names from others. If angular didn't use $, then
|
||||
evaluating a.length() would return undefined because neither a nor angular define such a property.
|
||||
Consider that in a future version of angular we might choose to add a length method, in which case
|
||||
the behavior of the expression would change. Worse yet, you the developer could create a length
|
||||
property and then we would have collision. This problem exists because angular augments existing
|
||||
objects with additional behavior. By prefixing its additions with $ we are reserving our namespace
|
||||
so that angular developers and developers who use angular can develop in harmony without
|
||||
collisions.
|
||||
|
||||
|
||||
@@ -34,3 +34,4 @@
|
||||
* service:$browser(mock)
|
||||
* {@link guide.downloading Downloading} - How to download, compile, and host the angular
|
||||
environment on your own server.
|
||||
* {@link guide.contribute Contributing} - How to contribute to angular project.
|
||||
|
||||
@@ -1,126 +1,153 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Overview
|
||||
@description
|
||||
|
||||
# What is angular?
|
||||
Angular teaches your old browser new tricks. It is what HTML would have been if it had been
|
||||
designed for building web applications.
|
||||
|
||||
Take a simple example of user input as shown below. If you were using just HTML and JavaScript to
|
||||
implement this form, you would need to define listeners, DOM updates, and complex input validators
|
||||
in order to update and format the result. In angular you can achieve the effect with zero lines
|
||||
of JavaScript code using a declarative approach. Click on the source tab of the example below to
|
||||
view the angular implementation of this form.
|
||||
* <a href="#H1_1">What Is Angular?</a>
|
||||
* <a href="#H1_3">The Angular Philosophy</a>
|
||||
* <a href="#H1_2">Anatomy Of An Angular App</a>
|
||||
* <a href="#H1_4">Why You Want Angular</a>
|
||||
* <a href="#H1_5">Angular's Ancestors</a>
|
||||
* <a href="#H1_6">Watch a Presentation About Angular</a>
|
||||
|
||||
|
||||
<a name="H1_1"></a>
|
||||
# What Is Angular?
|
||||
|
||||
The short answer: angular is a new, powerful, client-side technology that makes it much easier for
|
||||
you to create dynamic web sites and complex web apps, all without leaving the comfort of your HTML
|
||||
/ JavaScript home.
|
||||
|
||||
The long answer: it kind of depends on where you're coming from...
|
||||
|
||||
* If you're a web designer, you might perceive angular to be a sweet {@link guide.template
|
||||
templating} system, that doesn't get in your way and provides you with lots of nice built-ins that
|
||||
make it easier to do what you want to do.
|
||||
|
||||
* If you're a web developer, you might be thrilled that angular functions as an excellent web
|
||||
framework, one that assists you all the way through the development cycle.
|
||||
|
||||
* If you want to go deeper, you can immerse yourself in angular's extensible HTML {@link
|
||||
guide.compiler compiler} that runs in your browser. This compiler teaches your browser new tricks.
|
||||
|
||||
So then, angular's not just a templating system, but you can create fantastic templates with it;
|
||||
angular's not just a web framework, but it has a very nice one; and angular's not just an
|
||||
extensible HTML compiler, but it has one of those too. Let's put it this way: angular includes
|
||||
these parts along with some others; it evolved naturally from earlier occurrences of these forms;
|
||||
and thus angular is something far greater than the sum of its parts. It sounds like... it's alive!
|
||||
|
||||
## An Intro By Way of Example
|
||||
|
||||
Let's say that you are a web designer, and you've spent many thous — erm, hundreds of hours
|
||||
designing web sites. But at this point, the thought of doing DOM updates, writing listeners, and
|
||||
writing input validators, all to do something as simple as implementing a form!? You either don't
|
||||
want to go there in the first place or you've been there and the thrill is gone.
|
||||
|
||||
You could even be muttering to yourself as you hack another callback, "This is like building my own
|
||||
bike from scratch every time I want to ride to the store." But let's say a clever friend, who keeps
|
||||
tabs on these sorts of things, told you to check out angular.
|
||||
|
||||
So now here you are checking out angular, and here is a simple example. Note that it features only
|
||||
the templating aspect of angular, but this should suffice for now to quickly demonstrates how much
|
||||
easier life can be with angular:
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
QTY: <input name="qty" value="1" ng:validate="integer:0" ng:required/>
|
||||
*
|
||||
Cost: <input name="cost" value="19.95" ng:validate="number" ng:required/>
|
||||
=
|
||||
{{qty * cost | currency}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should show of angular binding', function(){
|
||||
expect(binding('qty * cost')).toEqual('$19.95');
|
||||
input('qty').enter('2');
|
||||
input('cost').enter('5.00');
|
||||
expect(binding('qty * cost')).toEqual('$10.00');
|
||||
});
|
||||
</doc:scenario>
|
||||
<doc:source>
|
||||
<h2>Bigg Bike Shop</h2>
|
||||
<hr>
|
||||
<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}}
|
||||
<hr>
|
||||
</doc:source>
|
||||
<!--
|
||||
<doc:scenario>
|
||||
it('should show of angular binding', function(){
|
||||
expect(binding('qty * cost')).toEqual('$19.95');
|
||||
input('qty').enter('2');
|
||||
input('cost').enter('5.00');
|
||||
expect(binding('qty * cost')).toEqual('$10.00');
|
||||
});
|
||||
</doc:scenario>
|
||||
-->
|
||||
</doc:example>
|
||||
|
||||
Angular is to AJAX apps as Ruby on Rails is to round trip apps.
|
||||
Go ahead, try out the Live Preview above. "Well I _declare_! It's a fully functioning form, with
|
||||
an instantly updating display, and input validation." Speaking of being declarative, let's walk
|
||||
through the example and look at the angular-related lines to see what's going on around here.
|
||||
|
||||
# Angular frees you from:
|
||||
* **Registering callbacks:** Registering callbacks clutters your code, and it makes it hard to see
|
||||
the forest from the trees. Removing common boilerplate code such as callbacks is advantageous
|
||||
because it leaves the JavaScript with a more succinct version of your code, better describing
|
||||
what your application does.
|
||||
* **Manipulating HTML DOM programatically:** Manipulating HTML DOM is a cornerstone of AJAX
|
||||
applications, but it is very cumbersome and error-prone. By declaratively describing how the UI
|
||||
should change as your application state changes, you are freed from low level DOM manipulation
|
||||
activities. Most applications written with angular never have to programatically manipulate
|
||||
the DOM.
|
||||
* **Marshaling data to and from the UI:** CRUD operations make up the majority of most AJAX
|
||||
applications. The flow of marshaling data from the server to an internal object to a HTML form,
|
||||
validating the form, displaying validation errors, returning to an internal model and then back
|
||||
to the server creates a lot of boilerplate code. angular eliminates almost all of this
|
||||
boilerplate. leaving code that is richer and describes the overall flow of the application
|
||||
rather than implementation details.
|
||||
* **Writing tons of initialization code just to get started:** Typically you need to write a lot
|
||||
of plumbing and initialization code just to get a basic "Hello World" AJAX app working. With
|
||||
angular you can bootstrap your app easily using services, which are auto-injected into your
|
||||
application in a GUICE-like dependency-injection style. This allows you to get started
|
||||
developing features quickly. As a bonus, you get full control over the initialization process
|
||||
in automated tests.
|
||||
In line __2__ of the example, we let the browser know about the angular namespace:
|
||||
|
||||
2 <html xmlns:ng="http://angularjs.org">
|
||||
|
||||
This ensures angular runs nicely in all major browsers.
|
||||
|
||||
In line __3__ we do two angular setup tasks inside a `<script>` tag:
|
||||
|
||||
1. We pull in `angular.js`.
|
||||
2. The angular {@link angular.directive.ng:autobind ng:autobind} directive tells angular to {@link
|
||||
guide.compiler compile} and manage the whole HTML document.
|
||||
|
||||
3 <script src="file:///Users/krculp/angular.js/build/angular.min.js" ng:autobind></script>
|
||||
|
||||
Lines __14__ and __15__ set up one side of angular's very cool two-way data binding, as well as
|
||||
demonstrate some easy input validation:
|
||||
|
||||
14 Quantity: <input name="qty" value="1" ng:validate="integer:0" ng:required/>
|
||||
15 Cost: <input name="cost" value="199.95" ng:validate="number" ng:required/>
|
||||
|
||||
These input widgets look normal enough, but consider these points:
|
||||
|
||||
* Remember the `ng:autobind` directive from line 3? 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" part of the Model-View-Controller design pattern.
|
||||
* Note the angular directives, {@link angular.widget.@ng:validate ng:validate} and {@link
|
||||
ngular.widget.@ng:required ng:required}. You may have noticed that when you enter invalid data or
|
||||
leave the the input fields blank, the borders turn a plainly irritated red color, and the display
|
||||
value disappears. These `ng:` directives make it easier to implement field validators than coding
|
||||
them in JavaScript, no? Yes.
|
||||
|
||||
And finally, the mysterious line #__19__:
|
||||
|
||||
19 Total: {{qty * cost | currency}}
|
||||
|
||||
What's with the curly braces? Those curly braces are your friend. This notation, `{{ _expression_
|
||||
}}`, is a bit of built-in angular {@link angular.markup markup}, a shortcut that you use to display
|
||||
data. The expression within curly braces gets transformed by the angular compiler into an angular
|
||||
directive ({@link angular.directive.ng:bind ng:bind}). The expression itself can be a combination
|
||||
of both an expression and a {@link angular.filter filter}: `{{ expression | filter }}`.
|
||||
|
||||
In our example above, we're saying, "Bind the data we got from the input widgets to the display,
|
||||
multiply them together, and format the resulting number into something that looks like money."
|
||||
|
||||
|
||||
# angular is/has:
|
||||
* **An HTML Compiler:** angular is an HTML compiler in the browser. It allows you to give meaning
|
||||
to any HTML element, attribute, or text and create new primitives as building blocks for your
|
||||
application.
|
||||
* **Declarative:** Declarative means that you describe what the page looks like rather than
|
||||
instructing how to draw the page. HTML is great at declaring static documents. Angular extends
|
||||
the declarative nature of HTML beyond static documents to define dynamic applications.
|
||||
* **Declarative Templates:** In angular, you write HTML to declare the view and UI templates for
|
||||
your application. You can express many common application constructs without using any
|
||||
JavaScript at all.
|
||||
* **Bidirectional Data Binding:** Allows your application to have a single source of truth
|
||||
(your model), where the HTML is a projection of the internal state of your application, which
|
||||
you can provide to the user in a declarative way.
|
||||
* **Built-in Services:** angular provides many standard AJAX operations to get you going quickly,
|
||||
and dependency-injection allows you to swap them out as needed. Common services include:
|
||||
Dependency Inject, History Management, URL Router, AJAX/XHR requests, and data caching,
|
||||
to name a few.
|
||||
* **Very testable:** Testing difficulty is dramatically affected by the way you structure your
|
||||
code. With angular, testability is baked in.
|
||||
<a name="H1_3"></a>
|
||||
# The Angular Philosophy
|
||||
|
||||
# angular is NOT a:
|
||||
* **Library:** You don't call its functions.
|
||||
* **Framework:** It does not call your functions.
|
||||
* **DOM Manipulation Library:** It does not provide a way to manipulate DOM, but does provide
|
||||
primitives to create UI projections of your data.
|
||||
* **Widget Library:** There are lots of existing widget libraries that you can integrate with
|
||||
angular.
|
||||
Angular is built around the belief that declarative code is better than imperative when it comes to
|
||||
building UIs and wiring software components together, while imperative code is clearly the way to
|
||||
go for expressing business logic.
|
||||
|
||||
|
||||
|
||||
# Not just another templating system
|
||||
|
||||
At the highest level, angular looks like a just another templating system, but there are few
|
||||
important reasons why angular is different and makes it a very good fit for application
|
||||
development.
|
||||
|
||||
Angular:
|
||||
* **Uses HTML/CSS syntax:** This makes it easy to read and can be edited with existing HTML/CSS
|
||||
authoring tools.
|
||||
* **Extends HTML vocabulary:** Angular allows you to create new HTML tags, which expand into
|
||||
dynamic UI components.
|
||||
* **Executes in the browser:** Removes the round trip to the server for many operations and
|
||||
creates instant feedback for users.
|
||||
* **Bidirectional data binding:** The model is the single source of truth. Programmatic changes
|
||||
to the model are automatically reflected in the view. Any changes by the user to the view are
|
||||
automatically reflected in the model.
|
||||
* **Services:** These allow for a reusable way of injecting dependencies into an application.
|
||||
* **MVC:** Clean separation between model-view-controller, which aids in understanding,
|
||||
maintenance, and testing of large systems.
|
||||
|
||||
|
||||
# The angular philosophy
|
||||
|
||||
Angular is built around the belief that declarative code is preferred over imperative when it
|
||||
comes to building UIs and connecting the pieces together.
|
||||
|
||||
As an example, if you wanted to add a new label to your application, you could do so by simply
|
||||
adding text to the HTML template, saving the code, and refreshing your browser:
|
||||
Not to put too fine a point on it, but if you wanted to add a new label to your application, you
|
||||
could do it by simply adding text to the HTML template, saving the code, and refreshing your
|
||||
browser (this here is declarative):
|
||||
|
||||
<pre>
|
||||
<span class="label">Hello</span>
|
||||
</pre>
|
||||
|
||||
In programmatic systems you would have to write and run code like this:
|
||||
Or, as In programmatic systems (like {@link http://code.google.com/webtoolkit/ GWT}), you would
|
||||
have to write the code and then run the code like this:
|
||||
|
||||
<pre>
|
||||
var label = new Label();
|
||||
@@ -129,38 +156,182 @@ label.setClass('label');
|
||||
parent.addChild(label);
|
||||
</pre>
|
||||
|
||||
## Benefits:
|
||||
That looks like, let's see, do some math, factor out the `<pre>`s, carry the one, ummm... a little
|
||||
bit of markup versus four times as much code.
|
||||
|
||||
* Compile-free: Change your template and logic code and reload the browser to see it run
|
||||
immediately. In contrast, programmatic serverside views often need to be compiled and executed
|
||||
to be viewed, which takes time. This dramatically increases the speed of your development cycle.
|
||||
* Declarative templates are easier to understand and change than programmatic instructions.
|
||||
* Declarative templates can be edited in existing HTML editors such as DreamWeaver, Eclipse,
|
||||
TextMate, Vim, etc.
|
||||
* Declarative templates can be edited by web designers without the need to work with web
|
||||
developers.
|
||||
More Angular Philosophy:
|
||||
|
||||
HTML is missing certain features, which angular adds via its compiler, thereby "teaching" the
|
||||
browser these new tricks:
|
||||
* It is a very good idea to decouple DOM manipulation from app logic. This dramatically improves
|
||||
the testability of the code.
|
||||
* It is a really, _really_ good idea to regard app testing as equal in importance to app writing.
|
||||
Testing difficulty is dramatically affected by the way the code is structured.
|
||||
* It is an excellent idea to decouple the client side of an app from the server side. This allows
|
||||
development work to progress in parallel, and allows for reuse of both sides.
|
||||
* It is very helpful indeed if the framework guides developers through the entire journey of
|
||||
building an app: from designing the UI, through writing the business logic, to testing.
|
||||
* It is always good to make common tasks trivial and difficult tasks possible.
|
||||
|
||||
* Dynamic behavior
|
||||
* Componentizing HTML snippets into reusable components
|
||||
* Dynamically include other HTML templates
|
||||
* Two-way data binding
|
||||
* Rich validation in forms
|
||||
* Model-View-Controller modularization
|
||||
Now that we're homing in on what angular is, perhaps now would be a good time to list a few things
|
||||
what angular isn't:
|
||||
|
||||
# Watch a presentation about angular
|
||||
* It's not a Library. You don't just call its functions, although it does provide you with some
|
||||
utility APIs.
|
||||
* It's not a DOM Manipulation Library. angular uses jQuery to manipulate the DOM behind the scenes,
|
||||
rather than give you functions to manipulate the DOM with yourself.
|
||||
* It's not a Widget Library. There are lots of existing widget libraries that you can integrate
|
||||
with angular.
|
||||
* It's not "Just Another Templating System". A part of angular is a templating system. The
|
||||
templating subsystem of angular is different from the traditional approach for these reasons:
|
||||
* It Uses HTML/CSS syntax: This makes it easy to read and can be edited with existing HTML/CSS
|
||||
authoring tools.
|
||||
* It Extends HTML vocabulary: Angular allows you to create new HTML tags, which expand into
|
||||
dynamic UI components.
|
||||
* It Executes in the browser: Removes the round trip to the server for many operations and
|
||||
creates instant feedback for users as well as developers.
|
||||
* It Has Bidirectional data binding: The model is the single source of truth. Programmatic
|
||||
changes to the model are automatically reflected in the view. Any changes by the user to the view
|
||||
are automatically reflected in the model.
|
||||
|
||||
|
||||
<a name="H1_2"></a>
|
||||
# Anatomy Of An Angular App
|
||||
|
||||
This section describes the parts of an angular app in more detail.
|
||||
|
||||
## Templates
|
||||
|
||||
{@link guide.template Templates} are the part of angular that makes it easy and fun to create the
|
||||
UI for your web apps. With angular's templates you can create a dynamic UI using only HTML and
|
||||
CSS, but now you can add your own elements, attributes, and markup. The angular compiler reads the
|
||||
"angularized" HTML when your page loads, and follows the instructions in there to generate a
|
||||
dynamic page for you. This is the View part of MVC. "But wait there's more": since the compiler is
|
||||
extensible, you can build your own declarative language on top of HTML!
|
||||
|
||||
## Application Logic and Behavior
|
||||
|
||||
Application Logic and Behavior, which you define in JavaScript, is the C in MVC. With angular you
|
||||
write the logic (the controllers) for your app, but because angular takes care of reflecting the
|
||||
state of the model in the view, you don't have to write listeners or DOM manipulators. This feature
|
||||
makes your application logic very easy to write, test, maintain, and understand.
|
||||
|
||||
## Data
|
||||
|
||||
In an angular app, all of your data is referenced from inside of a {@link angular.scope scope}.
|
||||
The scope is the data Model, the M in the MVC pattern. A scope is a JavaScript object that has
|
||||
watcher functions that keep tabs on the data that is referenced from that scope. The data could be
|
||||
one or more Javascript objects, arrays, or primitives, it doesn't matter. What matters is that
|
||||
these are all referenced by the scope.
|
||||
|
||||
This "scope thing" is how angular takes care of keeping your data model and your UI in sync.
|
||||
Whenever something occurs to change the state of the scope, angular immediately reflects that
|
||||
change in the UI, and vice versa.
|
||||
|
||||
In addition to the three components described above (the MVC bits), angular comes with a set of
|
||||
{@link angular.service Services} that are very helpful for building web apps. The services include
|
||||
the following features:
|
||||
|
||||
* You can extend and add application-specific behavior to services.
|
||||
* Services include Dependency-Injection, XHR, caching, URL routing, and browser abstraction.
|
||||
|
||||
The following illustration shows the parts of an angular application and how they work together:
|
||||
|
||||
<img class="left" src="img/angular_parts.png" border="0" />
|
||||
|
||||
|
||||
<a name="H1_4"></a>
|
||||
# Why You Want Angular
|
||||
|
||||
Angular frees you from the following pain:
|
||||
|
||||
* **Registering callbacks:** Registering callbacks clutters your code, making it hard to see the
|
||||
forest for the trees. Removing common boilerplate code such as callbacks is a good thing. It vastly
|
||||
reduces the amount of JavaScript coding _you_ have to do, and it makes it easier to see what your
|
||||
application does.
|
||||
* **Manipulating HTML DOM programatically:** Manipulating HTML DOM is a cornerstone of AJAX
|
||||
applications, but it's cumbersome and error-prone. By declaratively describing how the UI should
|
||||
change as your application state changes, you are freed from low level DOM manipulation tasks. Most
|
||||
applications written with angular never have to programatically manipulate the DOM, although you
|
||||
can if you want to, knock yourself out.
|
||||
* **Marshaling data to and from the UI:** CRUD operations make up the majority of AJAX
|
||||
applications. The flow of marshaling data from the server to an internal object to an HTML form,
|
||||
allowing users to modify the form, validating the form, displaying validation errors, returning to
|
||||
an internal model, and then back to the server (gah!) creates a lot of boilerplate code. Angular
|
||||
eliminates almost all of this boilerplate, leaving code that describes the overall flow of the
|
||||
application rather than all of the implementation details.
|
||||
* **Writing tons of initialization code just to get started:** Typically you need to write a lot of
|
||||
plumbing just to get a basic "Hello World" AJAX app working. With angular you can bootstrap your
|
||||
app easily using services, which are auto-injected into your application in a {@link
|
||||
http://code.google.com/p/google-guice/ Guice}-like dependency-injection style. This allows you to
|
||||
get started developing features quickly. As a bonus, you get full control over the initialization
|
||||
process in automated tests.
|
||||
|
||||
|
||||
<a name="H1_5"></a>
|
||||
# Angular's Ancestors
|
||||
|
||||
Where does angular come from? What events led to the inevitability of the appearance of something
|
||||
like angular?
|
||||
|
||||
## First There Was HTML
|
||||
|
||||
HTML was initially designed long, long ago, in the great year of 1989, with the intention to create
|
||||
a markup language for sharing scientific documents over the network. Yes, yes, certainly there was
|
||||
SGML even before that, but it was so difficult that even esteemed scientists balked at using it.
|
||||
Thankfully, Tim Berners-Lee saved all of us from that pain with his much friendlier HTML.
|
||||
`<HTML><BODY>Thank You, TB-L!</BODY></HTML>`.
|
||||
|
||||
## Then There Was JavaScript
|
||||
|
||||
Fast forward to 1995: JavaScript was invented. This was done with the best of intentions! But in
|
||||
practice it initially served mainly to annoy Internet users with cheap effects that "enhanced"
|
||||
static HTML documents.
|
||||
|
||||
Fast forward to the mid 2000s, when a new breed of back-then-considered-rich web applications
|
||||
started to appear on the web. These were built with HTML, JavaScript, and CSS, and featured less
|
||||
annoying and more impressive effects. Can you recall the first time you saw apps like Gmail, or
|
||||
Google Maps, and you couldn't believe everything that was going on in the browser?
|
||||
|
||||
## And JavaScript Prevailed
|
||||
|
||||
As of this writing, in 2011, people are building still richer and more interactive web applications
|
||||
that often rival their desktop counterparts. And yet they are essentially still working with
|
||||
technology and programming primitives that were used decades ago for the creation of static
|
||||
documents with cheap graphic effects. At the same time, the web is HUGE now, and we
|
||||
can't just abandon the technologies it was built with. Applets, Flash and Silverlight tried it, and
|
||||
in some ways succeeded. Yet many would argue that in reality they failed, because they tried to
|
||||
work _around_ the web instead of working _with_ it.
|
||||
|
||||
## And Then There Was Angular
|
||||
|
||||
Angular recognizes the strengths of the existing "static" web technologies, as well as their
|
||||
deficiencies. At the same time, angular is learning from the failures of other technologies that
|
||||
tried, or are trying, to work around the web.
|
||||
|
||||
For these reasons angular plays to the strengths of established web technologies, instead of
|
||||
bypassing them. Angular sets out the goal of increasing the abstraction and programming primitives
|
||||
that developers use to build web applications, so as to better reflect the needs of modern web
|
||||
applications and their developers.
|
||||
|
||||
|
||||
<a name="H1_6"></a>
|
||||
# Watch a Presentation About Angular
|
||||
|
||||
Here is an early presentation on angular, but note that substantial development has occurred since
|
||||
the talk was given in July of 2010.
|
||||
|
||||
<object width="480" height="385">
|
||||
<param name="movie" value="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"></param>
|
||||
<param name="allowFullScreen" value="true"></param>
|
||||
<param name="allowscriptaccess" value="always"></param>
|
||||
<embed src="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"
|
||||
type="application/x-shockwave-flash" allowscriptaccess="always"
|
||||
allowfullscreen="true" width="480" height="385"></embed>
|
||||
<param name="movie" value="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"></param>
|
||||
<param name="allowFullScreen" value="true"></param>
|
||||
<param name="allowscriptaccess" value="always"></param>
|
||||
<embed src="http://www.youtube.com/v/elvcgVSynRg&hl=en_US&fs=1"
|
||||
type="application/x-shockwave-flash" allowscriptaccess="always"
|
||||
allowfullscreen="true" width="480" height="385"></embed>
|
||||
</object>
|
||||
|
||||
{@link https://docs.google.com/present/edit?id=0Abz6S2TvsDWSZDQ0OWdjaF8yNTRnODczazdmZg&hl=en&authkey=CO-b7oID Presentation}
|
||||
{@link
|
||||
https://docs.google.com/present/edit?id=0Abz6S2TvsDWSZDQ0OWdjaF8yNTRnODczazdmZg&hl=en&authkey=CO-b7oID
|
||||
Presentation}
|
||||
|
|
||||
{@link https://docs.google.com/document/edit?id=1ZHVhqC0apbzPRQcgnb1Ye-bAUbNJ-IlFMyPBPCZ2cYU&hl=en&authkey=CInnwLYO Source}
|
||||
{@link
|
||||
https://docs.google.com/document/edit?id=1ZHVhqC0apbzPRQcgnb1Ye-bAUbNJ-IlFMyPBPCZ2cYU&hl=en&authkey=CInnwLYO
|
||||
Source}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Developer Guide: Testing
|
||||
@description
|
||||
|
||||
# Testing Angular Applications
|
||||
|
||||
to be written...
|
||||
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -7,34 +7,6 @@ describe('dom', function(){
|
||||
dom = new DOM();
|
||||
});
|
||||
|
||||
describe('example', function(){
|
||||
it('should render code, live, test', function(){
|
||||
dom.example('desc', 'src', 'scenario');
|
||||
expect(dom.toString()).toEqual(
|
||||
'<h1>Example</h1>\n' +
|
||||
'<div class="example">' +
|
||||
'desc<doc:example><doc:source>src</doc:source>\n' +
|
||||
'<doc:scenario>scenario</doc:scenario>\n'+
|
||||
'</doc:example>\n' +
|
||||
'</div>\n');
|
||||
});
|
||||
|
||||
it('should render non-live, test with description', function(){
|
||||
dom.example('desc', 'src', false);
|
||||
expect(dom.toString()).toEqual('<h1>Example</h1>\n' +
|
||||
'<div class="example">' +
|
||||
'desc<div ng:non-bindable="">' +
|
||||
'<pre class="brush: js; html-script: true;">src</pre>\n' +
|
||||
'</div>\n' +
|
||||
'</div>\n');
|
||||
});
|
||||
|
||||
it('should render non-live, test', function(){
|
||||
dom.example('desc', 'src', false);
|
||||
expect(dom.toString()).toContain('<pre class="brush: js; html-script: true;">src</pre>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('h', function(){
|
||||
|
||||
it('should render using function', function(){
|
||||
|
||||
@@ -74,12 +74,6 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
it('should not remove extra line breaks', function(){
|
||||
var doc = new Doc('@example\nA\n\nB');
|
||||
doc.parse();
|
||||
expect(doc.example).toEqual('A\n\nB');
|
||||
});
|
||||
|
||||
it('should parse filename', function(){
|
||||
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
|
||||
doc.parse(0);
|
||||
@@ -92,7 +86,7 @@ describe('ngdoc', function(){
|
||||
'<doc:source>\n<>\n</doc:source></doc:example> after');
|
||||
doc.parse();
|
||||
expect(doc.description).toContain('<p>before </p><doc:example>' +
|
||||
'<doc:source>\n<>\n</doc:source></doc:example><p>after</p>');
|
||||
'<pre class="doc-source">\n<>\n</pre></doc:example><p>after</p>');
|
||||
});
|
||||
|
||||
it('should escape <doc:scenario> element', function(){
|
||||
@@ -100,7 +94,7 @@ describe('ngdoc', function(){
|
||||
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
|
||||
doc.parse();
|
||||
expect(doc.description).toContain('<p>before </p><doc:example>' +
|
||||
'<doc:scenario>\n<>\n</doc:scenario></doc:example><p>after</p>');
|
||||
'<pre class="doc-scenario">\n<>\n</pre></doc:example><p>after</p>');
|
||||
});
|
||||
|
||||
describe('sorting', function(){
|
||||
@@ -128,32 +122,14 @@ describe('ngdoc', function(){
|
||||
});
|
||||
});
|
||||
|
||||
describe('scenario', function(){
|
||||
it('should render from @example/@scenario and <doc:example>', function(){
|
||||
var doc = new Doc(
|
||||
'@id id\n' +
|
||||
'@description <doc:example><doc:scenario>scenario0</doc:scenario></doc:example>' +
|
||||
'@example exempleText\n' +
|
||||
'@scenario scenario1\n' +
|
||||
'@scenario scenario2').parse();
|
||||
expect(ngdoc.scenarios([doc])).toContain('describe("id"');
|
||||
expect(ngdoc.scenarios([doc])).toContain('navigateTo("index.html#!id")');
|
||||
expect(ngdoc.scenarios([doc])).toContain('\n scenario0\n');
|
||||
expect(ngdoc.scenarios([doc])).toContain('\n scenario1\n');
|
||||
expect(ngdoc.scenarios([doc])).toContain('\n scenario2\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('markdown', function(){
|
||||
var markdown = ngdoc.markdown;
|
||||
|
||||
it('should replace angular in markdown', function(){
|
||||
expect(markdown('<angular/>')).
|
||||
expect(new Doc().markdown('<angular/>')).
|
||||
toEqual('<p><tt><angular/></tt></p>');
|
||||
});
|
||||
|
||||
it('should not replace anything in <pre>, but escape the html escape the content', function(){
|
||||
expect(markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
|
||||
expect(new Doc().markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
|
||||
toEqual(
|
||||
'<p>bah x</p>' +
|
||||
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
|
||||
@@ -163,10 +139,37 @@ describe('ngdoc', function(){
|
||||
});
|
||||
|
||||
it('should replace text between two <pre></pre> tags', function() {
|
||||
expect(markdown('<pre>x</pre># One<pre>b</pre>')).
|
||||
expect(new Doc().markdown('<pre>x</pre># One<pre>b</pre>')).
|
||||
toMatch('</div><h1>One</h1><div');
|
||||
});
|
||||
|
||||
it('should unindent text before processing based on the second line', function() {
|
||||
expect(new Doc().markdown('first line\n' +
|
||||
' second line\n\n' +
|
||||
' third line\n' +
|
||||
' fourth line\n\n' +
|
||||
' fifth line')).
|
||||
toMatch('<p>first line\n' +
|
||||
'second line</p>\n\n' +
|
||||
'<pre><code>third line\n' +
|
||||
' fourth line\n</code></pre>\n\n' +
|
||||
'<p>fifth line</p>');
|
||||
});
|
||||
|
||||
it('should unindent text before processing based on the first line', function() {
|
||||
expect(new Doc().markdown(' first line\n\n' +
|
||||
' second line\n' +
|
||||
' third line\n' +
|
||||
' fourth line\n\n' +
|
||||
' fifth line')).
|
||||
toMatch('<p>first line</p>\n\n' +
|
||||
'<pre><code>second line\n' +
|
||||
'third line\n' +
|
||||
' fourth line\n</code></pre>\n\n' +
|
||||
'<p>fifth line</p>');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('trim', function(){
|
||||
@@ -187,7 +190,7 @@ describe('ngdoc', function(){
|
||||
var methodB = new Doc({name:'methodB', methodOf:'angular.service.abc'});
|
||||
var propA = new Doc({name:'propA', propertyOf:'angular.service.abc'});
|
||||
var propB = new Doc({name:'propB', propertyOf:'angular.service.abc'});
|
||||
;var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order;
|
||||
var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order;
|
||||
ngdoc.merge(docs);
|
||||
expect(docs.length).toEqual(1);
|
||||
expect(docs[0].name).toEqual('angular.service.abc');
|
||||
@@ -209,7 +212,7 @@ describe('ngdoc', function(){
|
||||
name : 'number',
|
||||
optional: false,
|
||||
'default' : undefined,
|
||||
description : '<p>Number \n to format.</p>' }]);
|
||||
description : '<p>Number \nto format.</p>' }]);
|
||||
});
|
||||
|
||||
it('should parse with default and optional', function(){
|
||||
@@ -239,13 +242,13 @@ describe('ngdoc', function(){
|
||||
expect(doc.properties.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should parse @property with only name', function() {
|
||||
it('should not parse @property without a type', function() {
|
||||
var doc = new Doc("@property fake");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('fake');
|
||||
expect(function() { doc.parse(); }).
|
||||
toThrow(new Error("Not a valid 'property' format: fake"));
|
||||
});
|
||||
|
||||
it('should parse @property with optional type', function() {
|
||||
it('should parse @property with type', function() {
|
||||
var doc = new Doc("@property {string} name");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
@@ -253,10 +256,10 @@ describe('ngdoc', function(){
|
||||
});
|
||||
|
||||
it('should parse @property with optional description', function() {
|
||||
var doc = new Doc("@property name desc rip tion");
|
||||
var doc = new Doc("@property {string} name desc rip tion");
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].description).toEqual('desc rip tion');
|
||||
expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
|
||||
});
|
||||
|
||||
it('should parse @property with type and description both', function() {
|
||||
@@ -264,7 +267,7 @@ describe('ngdoc', function(){
|
||||
doc.parse();
|
||||
expect(doc.properties[0].name).toEqual('name');
|
||||
expect(doc.properties[0].type).toEqual('bool');
|
||||
expect(doc.properties[0].description).toEqual('desc rip tion');
|
||||
expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -291,7 +294,7 @@ describe('ngdoc', function(){
|
||||
var doc = new Doc("@returns {string} description\n new line\n another line");
|
||||
doc.parse();
|
||||
expect(doc.returns).
|
||||
toEqual({type: 'string', description: '<p>description\n new line\n another line</p>'});
|
||||
toEqual({type: 'string', description: '<p>description\nnew line\nanother line</p>'});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -319,6 +322,8 @@ describe('ngdoc', function(){
|
||||
var doc = new Doc("@description " +
|
||||
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
|
||||
'dad{@link angular.foo}\n\n' +
|
||||
'external{@link http://angularjs.org}\n\n' +
|
||||
'external{@link ./static.html}\n\n' +
|
||||
'{@link angular.directive.ng:foo ng:foo}');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
@@ -329,6 +334,18 @@ describe('ngdoc', function(){
|
||||
toContain('dad<a href="#!angular.foo"><code>angular.foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!angular.directive.ng:foo"><code>ng:foo</code></a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
|
||||
expect(doc.description).
|
||||
toContain('<a href="./static.html">./static.html</a>');
|
||||
});
|
||||
|
||||
it('shoul support line breaks in @link', function(){
|
||||
var doc = new Doc("@description " +
|
||||
'{@link\nurl\na\nb}');
|
||||
doc.parse();
|
||||
expect(doc.description).
|
||||
toContain('<a href="#!url">a b</a>');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -337,34 +354,20 @@ describe('ngdoc', function(){
|
||||
it('should not remove {{}}', function(){
|
||||
var doc = new Doc('@example text {{ abc }}');
|
||||
doc.parse();
|
||||
expect(doc.example).toEqual('text {{ abc }}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('@exampleDescription', function(){
|
||||
it('should render example description', function(){
|
||||
var doc = new Doc('@exampleDescription some\n text');
|
||||
doc.ngdoc = "filter";
|
||||
doc.parse();
|
||||
expect(doc.html()).toContain('<p>some\n text');
|
||||
expect(doc.example).toEqual('<p>text {{ abc }}</p>');
|
||||
});
|
||||
|
||||
it('should alias @exampleDescription to @exampleDesc', function(){
|
||||
var doc = new Doc('@exampleDesc some\n text');
|
||||
doc.ngdoc = "filter";
|
||||
doc.parse();
|
||||
expect(doc.html()).toContain('<p>some\n text');
|
||||
it('should support doc:example', function(){
|
||||
var doc = new Doc('@ngdoc overview\n@example \n' +
|
||||
'<doc:example>\n' +
|
||||
' <doc:source><escapeme></doc:source>\n' +
|
||||
' <doc:scenario><scenario></doc:scenario>\n' +
|
||||
'</doc:example>').parse();
|
||||
var html = doc.html();
|
||||
expect(html).toContain('<pre class="doc-source"><escapeme></pre>');
|
||||
expect(html).toContain('<pre class="doc-scenario"><scenario></pre>');
|
||||
expect(doc.scenarios).toEqual(['<scenario>']);
|
||||
});
|
||||
|
||||
it('should render description in related method', function(){
|
||||
var doc = new Doc();
|
||||
doc.ngdoc = 'service';
|
||||
doc.methods = [new Doc('@ngdoc method\n@exampleDescription MDesc\n@example MExmp').parse()];
|
||||
doc.properties = [new Doc('@ngdoc property\n@exampleDescription PDesc\n@example PExmp').parse()];
|
||||
expect(doc.html()).toContain('<p>MDesc</p><div ng:non-bindable=""><pre class="brush: js; html-script: true;">MExmp</pre>');
|
||||
expect(doc.html()).toContain('<p>PDesc</p><div ng:non-bindable=""><pre class="brush: js; html-script: true;">PExmp</pre>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('@depricated', function() {
|
||||
|
||||
@@ -7,8 +7,8 @@ describe('sitemap', function(){
|
||||
var map = new SiteMap([]);
|
||||
expect(map.render()).toEqual([
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
||||
'</sitemapindex>', ''].join('\n'));
|
||||
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
||||
'</urlset>', ''].join('\n'));
|
||||
});
|
||||
|
||||
it('should render ngdoc url', function(){
|
||||
|
||||
@@ -10,12 +10,12 @@ function SiteMap(docs){
|
||||
this.render = function(){
|
||||
var map = [];
|
||||
map.push('<?xml version="1.0" encoding="UTF-8"?>');
|
||||
map.push('<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
|
||||
map.push('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
|
||||
docs.forEach(function(doc){
|
||||
map.push(' <url><loc>http://docs.angularjs.org/#!' +
|
||||
encode(doc.name) + '</loc><changefreq>weekly</changefreq></url>');
|
||||
});
|
||||
map.push('</sitemapindex>');
|
||||
map.push('</urlset>');
|
||||
map.push('');
|
||||
return map.join('\n');
|
||||
};
|
||||
|
||||
@@ -74,25 +74,8 @@ DOM.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
example: function(description, source, scenario) {
|
||||
if (description || source || scenario) {
|
||||
this.h('Example', function(){
|
||||
if (description)
|
||||
this.html(description);
|
||||
if (scenario === false) {
|
||||
this.code(source);
|
||||
} else {
|
||||
this.tag('doc:example', function(){
|
||||
if (source) this.tag('doc:source', source);
|
||||
if (scenario) this.tag('doc:scenario', scenario);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
h: function(heading, content, fn){
|
||||
if (content==undefined || content && content.legth == 0) return;
|
||||
if (content==undefined || (content instanceof Array && content.length == 0)) return;
|
||||
this.headingDepth++;
|
||||
this.tag('h' + this.headingDepth, heading);
|
||||
var className = typeof heading == 'string'
|
||||
|
||||
@@ -24,7 +24,8 @@ var writes = callback.chain(function(){
|
||||
});
|
||||
var metadata = ngdoc.metadata(docs);
|
||||
writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata).replace(/{/g, '\n{'), ';'], writes.waitFor());
|
||||
writer.copyImages(writes.waitFor());
|
||||
writer.copyDir('img', writes.waitFor());
|
||||
writer.copyDir('static', writes.waitFor());
|
||||
writer.copy('index.html', writes.waitFor());
|
||||
writer.copy('docs.js', writes.waitFor());
|
||||
writer.copy('docs.css', writes.waitFor());
|
||||
|
||||
@@ -7,7 +7,6 @@ var DOM = require('dom.js').DOM;
|
||||
var htmlEscape = require('dom.js').htmlEscape;
|
||||
var NEW_LINE = /\n\r?/;
|
||||
|
||||
exports.markdown = markdown;
|
||||
exports.trim = trim;
|
||||
exports.metadata = metadata;
|
||||
exports.scenarios = scenarios;
|
||||
@@ -25,6 +24,11 @@ function Doc(text, file, line) {
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
}
|
||||
this.scenarios = this.scenarios || [];
|
||||
this.requires = this.requires || [];
|
||||
this.param = this.param || [];
|
||||
this.properties = this.properties || [];
|
||||
this.methods = this.methods || [];
|
||||
}
|
||||
Doc.METADATA_IGNORE = (function(){
|
||||
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
|
||||
@@ -53,16 +57,55 @@ Doc.prototype = {
|
||||
return words.join(' ');
|
||||
},
|
||||
|
||||
markdown: function (text) {
|
||||
var self = this;
|
||||
var IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/;
|
||||
var IS_ANGULAR = /^angular\./;
|
||||
if (!text) return text;
|
||||
|
||||
text = trim(text);
|
||||
|
||||
var parts = text.split(/(<pre>[\s\S]*?<\/pre>|<doc:example>[\s\S]*?<\/doc:example>)/);
|
||||
|
||||
parts.forEach(function(text, i){
|
||||
if (text.match(/^<pre>/)) {
|
||||
text = text.replace(/^<pre>([\s\S]*)<\/pre>/mi, function(_, content){
|
||||
return '<div ng:non-bindable><pre class="brush: js; html-script: true;">' +
|
||||
content.replace(/</g, '<').replace(/>/g, '>') +
|
||||
'</pre></div>';
|
||||
});
|
||||
} else if (text.match(/^<doc:example>/)) {
|
||||
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
|
||||
function(_, before, content, after){
|
||||
return '<pre class="doc-source">' + htmlEscape(content) + '</pre>';
|
||||
});
|
||||
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
|
||||
function(_, before, content, after){
|
||||
self.scenarios.push(content);
|
||||
return '<pre class="doc-scenario">' + htmlEscape(content) + '</pre>';
|
||||
});
|
||||
} else {
|
||||
text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>');
|
||||
text = text.replace(/{@link\s+([^\s}]+)\s*([^}]*?)\s*}/g,
|
||||
function(_all, url, title){
|
||||
return '<a href="' + (url.match(IS_URL) ? '' : '#!') + url + '">'
|
||||
+ (url.match(IS_ANGULAR) ? '<code>' : '')
|
||||
+ (title || url).replace(/\n/g, ' ')
|
||||
+ (url.match(IS_ANGULAR) ? '</code>' : '')
|
||||
+ '</a>';
|
||||
});
|
||||
text = new Showdown.converter().makeHtml(text);
|
||||
}
|
||||
parts[i] = text;
|
||||
});
|
||||
return parts.join('');
|
||||
},
|
||||
|
||||
parse: function(){
|
||||
var atName;
|
||||
var atText;
|
||||
var match;
|
||||
var self = this;
|
||||
this.scenarios = [];
|
||||
this.requires = [];
|
||||
this.param = [];
|
||||
this.properties = [];
|
||||
this.methods = [];
|
||||
self.text.split(NEW_LINE).forEach(function(line){
|
||||
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
|
||||
// we found @name ...
|
||||
@@ -82,9 +125,9 @@ Doc.prototype = {
|
||||
this.id = this.id // if we have an id just use it
|
||||
|| (((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // try to extract it from file name
|
||||
|| this.name; // default to name
|
||||
this.description = markdown(this.description);
|
||||
this['this'] = markdown(this['this']);
|
||||
this.exampleDescription = markdown(this.exampleDescription || this.exampleDesc);
|
||||
this.description = this.markdown(this.description);
|
||||
this.example = this.markdown(this.example);
|
||||
this['this'] = this.markdown(this['this']);
|
||||
return this;
|
||||
|
||||
function flush(){
|
||||
@@ -98,7 +141,7 @@ Doc.prototype = {
|
||||
}
|
||||
var param = {
|
||||
name: match[5] || match[4],
|
||||
description:markdown(text.replace(match[0], match[7])),
|
||||
description:self.markdown(text.replace(match[0], match[7])),
|
||||
type: match[1],
|
||||
optional: !!match[2],
|
||||
'default':match[6]
|
||||
@@ -111,27 +154,19 @@ Doc.prototype = {
|
||||
}
|
||||
self.returns = {
|
||||
type: match[1],
|
||||
description: markdown(text.replace(match[0], match[2]))
|
||||
description: self.markdown(text.replace(match[0], match[2]))
|
||||
};
|
||||
} else if(atName == 'description') {
|
||||
text.replace(/<doc:scenario>([\s\S]*)<\/doc:scenario>/mi,
|
||||
function(_, scenario){
|
||||
self.scenarios.push(scenario);
|
||||
});
|
||||
self.description = text;
|
||||
} else if(atName == 'requires') {
|
||||
self.requires.push(text);
|
||||
} else if(atName == 'scenario') {
|
||||
self.scenarios.push(text);
|
||||
} else if(atName == 'property') {
|
||||
var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
|
||||
var match = text.match(/^{(\S+)}\s+(\S+)(\s+(.*))?/);
|
||||
if (!match) {
|
||||
throw new Error("Not a valid 'property' format: " + text);
|
||||
}
|
||||
var property = {
|
||||
type: match[2],
|
||||
name: match[3],
|
||||
description: match[5] || ''
|
||||
type: match[1],
|
||||
name: match[2],
|
||||
description: self.markdown(text.replace(match[0], match[4]))
|
||||
};
|
||||
self.properties.push(property);
|
||||
} else {
|
||||
@@ -154,7 +189,7 @@ Doc.prototype = {
|
||||
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
|
||||
}).call(self, dom);
|
||||
|
||||
dom.example(self.exampleDescription, self.example, self.scenario);
|
||||
dom.h('Example', self.example, dom.html);
|
||||
});
|
||||
|
||||
return dom.toString();
|
||||
@@ -399,21 +434,37 @@ Doc.prototype = {
|
||||
},
|
||||
|
||||
html_usage_service: function(dom){
|
||||
var self = this;
|
||||
dom.h('Description', this.description, dom.html);
|
||||
dom.h('Dependencies', this.requires);
|
||||
|
||||
if (this.param.length) {
|
||||
dom.h('Usage', function(){
|
||||
dom.code(function(){
|
||||
dom.text(self.name.split('.').pop());
|
||||
dom.text('(');
|
||||
self.parameters(dom, ', ');
|
||||
dom.text(');');
|
||||
});
|
||||
|
||||
self.html_usage_parameters(dom);
|
||||
self.html_usage_this(dom);
|
||||
self.html_usage_returns(dom);
|
||||
});
|
||||
}
|
||||
|
||||
dom.h('Methods', this.methods, function(method){
|
||||
var signature = (method.param || []).map(property('name'));
|
||||
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
|
||||
dom.html(method.description);
|
||||
method.html_usage_parameters(dom);
|
||||
dom.example(method.exampleDescription, method.example, false);
|
||||
dom.h('Example', method.example, dom.html);
|
||||
});
|
||||
});
|
||||
dom.h('Properties', this.properties, function(property){
|
||||
dom.h(property.name, function(){
|
||||
dom.text(property.description);
|
||||
dom.example(property.exampleDescription, property.example, false);
|
||||
dom.html(property.description);
|
||||
dom.h('Example', property.example, dom.html);
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -436,45 +487,6 @@ Doc.prototype = {
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function markdown (text) {
|
||||
if (!text) return text;
|
||||
var parts = text.split(/(<pre>[\s\S]*?<\/pre>|<doc:example>[\s\S]*?<\/doc:example>)/),
|
||||
match;
|
||||
|
||||
parts.forEach(function(text, i){
|
||||
if (text.match(/^<pre>/)) {
|
||||
text = text.replace(/^<pre>([\s\S]*)<\/pre>/mi, function(_, content){
|
||||
return '<div ng:non-bindable><pre class="brush: js; html-script: true;">' +
|
||||
content.replace(/</g, '<').replace(/>/g, '>') +
|
||||
'</pre></div>';
|
||||
});
|
||||
} else if (text.match(/^<doc:example>/)) {
|
||||
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
|
||||
function(_, before, content, after){
|
||||
return before + htmlEscape(content) + after;
|
||||
});
|
||||
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
|
||||
function(_, before, content, after){
|
||||
return before + htmlEscape(content) + after;
|
||||
});
|
||||
} else {
|
||||
text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>');
|
||||
text = text.replace(/{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/gm,
|
||||
function(_all, url, _2, _3, title){
|
||||
return '<a href="#!' + url + '">'
|
||||
+ (url.match(/^angular\./) ? '<code>' : '')
|
||||
+ (title || url)
|
||||
+ (url.match(/^angular\./) ? '</code>' : '')
|
||||
+ '</a>';
|
||||
});
|
||||
text = new Showdown.converter().makeHtml(text);
|
||||
}
|
||||
parts[i] = text;
|
||||
});
|
||||
return parts.join('');
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function scenarios(docs){
|
||||
var specs = [];
|
||||
@@ -485,7 +497,7 @@ function scenarios(docs){
|
||||
specs.push(' });');
|
||||
specs.push('');
|
||||
doc.scenarios.forEach(function(scenario){
|
||||
specs.push(trim(scenario, ' '));
|
||||
specs.push(indent(trim(scenario), 2));
|
||||
specs.push('');
|
||||
});
|
||||
specs.push('});');
|
||||
@@ -521,7 +533,8 @@ function metadata(docs){
|
||||
}
|
||||
|
||||
var KEYWORD_PRIORITY = {
|
||||
'.guide': 1,
|
||||
'.started': 1,
|
||||
'.guide': 2,
|
||||
'.guide.overview': 1,
|
||||
'.angular': 7,
|
||||
'.angular.Array': 7,
|
||||
@@ -553,36 +566,57 @@ function keywordSort(a, b){
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
function trim(text, prefix) {
|
||||
var MAX = 9999;
|
||||
function trim(text) {
|
||||
var MAX_INDENT = 9999;
|
||||
var empty = RegExp.prototype.test.bind(/^\s*$/);
|
||||
var lines = text.split('\n');
|
||||
var minIndent = MAX;
|
||||
prefix = prefix || '';
|
||||
var minIndent = MAX_INDENT;
|
||||
var indentRegExp;
|
||||
var ignoreLine = (lines[0][0] != ' ' && lines.length > 1);
|
||||
// ignore first line if it has no indentation and there is more than one line
|
||||
|
||||
lines.forEach(function(line){
|
||||
minIndent = Math.min(minIndent, indent(line));
|
||||
if (ignoreLine) {
|
||||
ignoreLine = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var indent = line.match(/^\s*/)[0].length;
|
||||
if (indent > 0 || minIndent == MAX_INDENT) {
|
||||
minIndent = Math.min(minIndent, indent);
|
||||
}
|
||||
});
|
||||
|
||||
indentRegExp = new RegExp('^\\s{0,' + minIndent + '}');
|
||||
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
lines[i] = prefix + lines[i].substring(minIndent);
|
||||
lines[i] = lines[i].replace(indentRegExp, '');
|
||||
}
|
||||
|
||||
// remove leading lines
|
||||
while (empty(lines[0])) {
|
||||
lines.shift();
|
||||
}
|
||||
|
||||
// remove trailing
|
||||
while (empty(lines[lines.length - 1])) {
|
||||
lines.pop();
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function indent(line) {
|
||||
for(var i = 0; i < line.length; i++) {
|
||||
if (line.charAt(i) != ' ') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MAX;
|
||||
}
|
||||
function indent(text, spaceCount) {
|
||||
var lines = text.split('\n'),
|
||||
indent = '',
|
||||
fixedLines = [];
|
||||
|
||||
while(spaceCount--) indent += ' ';
|
||||
|
||||
lines.forEach(function(line) {
|
||||
fixedLines.push(indent + line);
|
||||
});
|
||||
|
||||
return fixedLines.join('\n');
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
var angularJsUrl;
|
||||
var scripts = document.getElementsByTagName("script");
|
||||
var filename = /(.*\/)angular([^\/]*)/;
|
||||
var angularJsRegex = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/;
|
||||
for(var j = 0; j < scripts.length; j++) {
|
||||
var src = scripts[j].src;
|
||||
if (src && src.match(filename)) {
|
||||
if (src && src.match(angularJsRegex)) {
|
||||
angularJsUrl = src.replace('docs.angularjs.org', 'code.angularjs.org');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,22 +25,27 @@
|
||||
this.descend(true); //compile the example code
|
||||
element.hide();
|
||||
|
||||
var example = element.find('doc\\:source').eq(0),
|
||||
var example = element.find('pre.doc-source').eq(0),
|
||||
exampleSrc = example.text(),
|
||||
scenario = element.find('doc\\:scenario').eq(0);
|
||||
scenario = element.find('pre.doc-scenario').eq(0);
|
||||
|
||||
var code = indent(exampleSrc);
|
||||
var tabs = angular.element(
|
||||
'<ul class="doc-example">' +
|
||||
'<li class="doc-example-heading"><h3>Source</h3></li>' +
|
||||
'<li class="doc-example-source" ng:non-bindable>' +
|
||||
'<pre class="brush: js; html-script: true; highlight: [' +
|
||||
code.hilite + ']; toolbar: false;"></pre></li>' +
|
||||
'<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
|
||||
'<li class="doc-example-live">' + exampleSrc +'</li>' +
|
||||
'<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
|
||||
'<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>' +
|
||||
'</ul>');
|
||||
var tabHtml =
|
||||
'<ul class="doc-example">' +
|
||||
'<li class="doc-example-heading"><h3>Source</h3></li>' +
|
||||
'<li class="doc-example-source" ng:non-bindable>' +
|
||||
'<pre class="brush: js; html-script: true; highlight: [' +
|
||||
code.hilite + ']; toolbar: false;"></pre></li>' +
|
||||
'<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
|
||||
'<li class="doc-example-live">' + exampleSrc +'</li>';
|
||||
if (scenario.text()) {
|
||||
tabHtml +=
|
||||
'<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
|
||||
'<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>';
|
||||
}
|
||||
tabHtml +=
|
||||
'</ul>';
|
||||
var tabs = angular.element(tabHtml);
|
||||
|
||||
tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html));
|
||||
|
||||
@@ -56,7 +62,8 @@
|
||||
});
|
||||
|
||||
function indent(text) {
|
||||
var lines = text.split(/\n/);
|
||||
if (!text) return text;
|
||||
var lines = text.split(/[\n|\r]/);
|
||||
var lineNo = [];
|
||||
// remove any leading blank lines
|
||||
while (lines[0].match(/^\s*$/)) lines.shift();
|
||||
|
||||
@@ -123,7 +123,7 @@ a {
|
||||
border-bottom: 1px solid #888;
|
||||
}
|
||||
|
||||
#main li > p,
|
||||
#main ul.parameters li > p,
|
||||
#main div.returns > p {
|
||||
display: inline; /* the top most paragraph should not cause new lines inside lists. */
|
||||
}
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
var HAS_HASH = /#/;
|
||||
DocsController.$inject = ['$location', '$browser', '$window'];
|
||||
function DocsController($location, $browser, $window) {
|
||||
this.pages = NG_PAGES;
|
||||
window.$root = this.$root;
|
||||
this.$location = $location;
|
||||
|
||||
if (!HAS_HASH.test($location.href)) {
|
||||
$location.hashPath = '!angular';
|
||||
}
|
||||
|
||||
this.$watch('$location.hashPath', function(hashPath) {
|
||||
if (hashPath.match(/^!/)) {
|
||||
this.partialId = hashPath.substring(1);
|
||||
this.partialTitle = (angular.Array.filter(NG_PAGES, {id:this.partialId})[0]||{}).name;
|
||||
}
|
||||
});
|
||||
|
||||
this.getUrl = function(page){
|
||||
return '#!' + page.id;
|
||||
};
|
||||
|
||||
this.getCurrentPartial = function(){
|
||||
return './' + this.getTitle() + '.html';
|
||||
};
|
||||
|
||||
this.getTitle = function(){
|
||||
var hashPath = $location.hashPath || '!angular';
|
||||
if (hashPath.match(/^!/)) {
|
||||
this.partialTitle = hashPath.substring(1);
|
||||
}
|
||||
return this.partialTitle;
|
||||
return './' + this.partialId + '.html';
|
||||
};
|
||||
|
||||
this.getClass = function(page) {
|
||||
var depth = page.depth,
|
||||
cssClass = 'level-' + depth + (page.name == this.getTitle() ? ' selected' : '');
|
||||
cssClass = 'level-' + depth + (page.name == this.partialId ? ' selected' : '');
|
||||
|
||||
if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular';
|
||||
|
||||
@@ -40,4 +45,9 @@ function DocsController($location, $browser, $window) {
|
||||
|
||||
}
|
||||
|
||||
// prevent compilation of code
|
||||
angular.widget('code', function(element){
|
||||
element.attr('ng:non-bindable', 'true');
|
||||
});
|
||||
|
||||
SyntaxHighlighter['defaults'].toolbar = false;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
xmlns:doc="http://docs.angularjs.org/"
|
||||
ng:controller="DocsController">
|
||||
<head>
|
||||
<title ng:bind-template="<angular/>: {{getTitle()}}"><angular/></title>
|
||||
<title ng:bind-template="<angular/>: {{partialTitle}}"><angular/></title>
|
||||
|
||||
<meta name="fragment" content="!">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<body style="display:none;" ng:show="true">
|
||||
<div id="header">
|
||||
<h1>
|
||||
<span class="main-title">{{getTitle()}}</span>
|
||||
<span class="main-title">{{partialTitle}}</span>
|
||||
<a href="#" tabindex="0"><span class="angular"><angular/></span> Docs</a>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -61,14 +61,12 @@ function copy(from, to, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
exports.copyImages = function(callback) {
|
||||
exports.makeDir(OUTPUT_DIR + '/img', callback.waitFor(function(){
|
||||
fs.readdir('docs/img', callback.waitFor(function(err, files){
|
||||
exports.copyDir = function(dir, callback) {
|
||||
exports.makeDir(OUTPUT_DIR + '/' + dir, callback.waitFor(function(){
|
||||
fs.readdir('docs/' + dir, callback.waitFor(function(err, files){
|
||||
if (err) return this.error(err);
|
||||
files.forEach(function(file){
|
||||
if (file.match(/\.(png|gif|jpg|jpeg)$/)) {
|
||||
copy('docs/img/' + file, OUTPUT_DIR + '/img/' + file, callback.waitFor());
|
||||
}
|
||||
copy('docs/' + dir + '/' + file, OUTPUT_DIR + '/' + dir + '/' + file, callback.waitFor());
|
||||
});
|
||||
callback();
|
||||
}));
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
@workInProgress
|
||||
@ngdoc overview
|
||||
@name Getting Started
|
||||
@description
|
||||
|
||||
# Hello World!
|
||||
|
||||
A great way for you to get started with `angular` is to create the tradtional
|
||||
"Hello World!" app:
|
||||
|
||||
1. In your favorite text editor, create an HTML file
|
||||
(for example, `helloworld.html`).
|
||||
2. From the __Source__ box below, copy and paste the code into your HTML file.
|
||||
(Double-click on the source to easily select all.)
|
||||
3. Open the file in your web browser.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Hello {{'World'}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
The resulting web page should look something like the following:
|
||||
|
||||
<img class="center" src="img/helloworld.png" border="1" />
|
||||
|
||||
Now let's take a closer look at that code, and see what is going on behind
|
||||
the scenes.
|
||||
|
||||
The first line of interest defines the `ng` namespace, which makes
|
||||
`angular` work across all browsers (especially important for IE):
|
||||
|
||||
<pre>
|
||||
<html xmlns:ng="http://angularjs.org">
|
||||
</pre>
|
||||
|
||||
The next line downloads the `angular` script, and instructs `angular` to process
|
||||
the entire HTML page when it is loaded:
|
||||
|
||||
<pre>
|
||||
<script type="text/javascript" src="http://code.angularjs.org/angular-?.?.?.min.js" ng:autobind></script>
|
||||
</pre>
|
||||
|
||||
(For details on what happens when `angular` processes an HTML page,
|
||||
see {@link guide.bootstrap Bootstrap}.)
|
||||
|
||||
Finally, this line in the `<body>` of the page is the template that describes
|
||||
how to display our greeting in the UI:
|
||||
|
||||
<pre>
|
||||
Hello {{'World'}}!
|
||||
</pre>
|
||||
|
||||
Note the use of the double curly brace markup (`{{ }}`) to bind the expression to
|
||||
the greeting text. Here the expression is the string literal 'World'.
|
||||
|
||||
Next let's look at a more interesting example, that uses `angular` to
|
||||
bind a dynamic expression to our greeting text.
|
||||
|
||||
# Hello <angular/> World!
|
||||
|
||||
This example demonstrates `angular`'s two-way data binding:
|
||||
|
||||
1. Edit the HTML file you created in the "Hello World!" example above.
|
||||
2. Replace the contents of `<body>` with the code from the __Source__ box below.
|
||||
3. Refresh your browswer window.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Your name: <input type="text" name="yourname" value="World"/>
|
||||
<hr/>
|
||||
Hello {{yourname}}!
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
|
||||
After the refresh, the page should look something like this:
|
||||
|
||||
<img class="left" src="img/helloworld_2way.png" border="1" />
|
||||
|
||||
These are some of the important points to note from this example:
|
||||
|
||||
* The text input {@link angular.widget widget} called `yourname` is bound to a model variable called
|
||||
`yourname`.
|
||||
* The double curly braces notation binds the variable `yourname` to the
|
||||
greeting text.
|
||||
<!--
|
||||
* The variable `yourname` is implicitly created in the root scope.
|
||||
-->
|
||||
* You did not need to explicitly register an event listener or define an event
|
||||
handler for events!
|
||||
|
||||
Now try typing your name into the input box, and notice the immediate change to
|
||||
the displayed greeting. This demonstrates the concept of `angular`'s
|
||||
{@link guide.data-binding bi-directional data binding}. Any changes to the input field are immediately
|
||||
reflected in the model (one direction), and any changes to the model are
|
||||
reflected in the greeting text (the other direction).
|
||||
|
||||
|
||||
# Anatomy of an `angular` App
|
||||
|
||||
This section describes the 3 parts of an `angular` app, and explains how they
|
||||
map to the Model-View-Controller design pattern:
|
||||
|
||||
## Templates
|
||||
|
||||
Templates, which you write in HTML and CSS, serve as the View. You add elements,
|
||||
attributes, and markup to HTML, which serve as instructions to the `angular`
|
||||
compiler. The `angular` compiler is fully extensible, meaning that with angular
|
||||
you can build your own declarative language on top of HTML!
|
||||
|
||||
## Application Logic and Behavior
|
||||
|
||||
Application Logic and Behavior, which you define in JavaScript, serve as the
|
||||
Controller. With `angular` (unlike with standard AJAX applications) you don't
|
||||
need to write additional listeners or DOM manipulators, because they are built-in.
|
||||
This feature makes your application logic very easy to write, test, maintain, and
|
||||
understand.
|
||||
|
||||
## Scope
|
||||
|
||||
The Model consists of one or more JavaScript objects, arrays, or primitive types.
|
||||
These are referenced from the scope. There are no restrictions on what the Model
|
||||
can be or what structure it should have. The only requirement is that it is
|
||||
referenced by the scope.
|
||||
|
||||
The following illustration shows the parts of an `angular` application and how they
|
||||
work together:
|
||||
|
||||
<img class="left" src="img/angular_parts.png" border="0" />
|
||||
|
||||
In addition, `angular` comes with a set of Services, which have the following
|
||||
properties:
|
||||
|
||||
* The services provided are very useful for building web applications.
|
||||
* You can extend and add application-specific behavior to services.
|
||||
* Services include Dependency-Injection, XHR, caching, URL routing,
|
||||
and browser abstraction.
|
||||
|
||||
# Where To Go Next
|
||||
|
||||
* For additional hands-on examples of using `angular`, including more source
|
||||
code that you can copy and paste into your own pages, take a look through
|
||||
the `angular` {@link Cookbook}.
|
||||
|
||||
* For explanations of the `angular` concepts presented in the examples on this
|
||||
page, see the {@link guide Developer Guide}.
|
||||
@@ -0,0 +1,18 @@
|
||||
<label>Name:</label>
|
||||
<input type="text" name="form.name" ng:required>
|
||||
|
||||
<div ng:repeat="contact in form.contacts">
|
||||
<select name="contact.type">
|
||||
<option>url</option>
|
||||
<option>email</option>
|
||||
<option>phone</option>
|
||||
</select>
|
||||
<input type="text" name="contact.url">
|
||||
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
|
||||
</div>
|
||||
<div>
|
||||
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
|
||||
</div>
|
||||
|
||||
<button ng:click="cancel()">Cancel</button>
|
||||
<button ng:click="save()">Save</button>
|
||||
@@ -0,0 +1,5 @@
|
||||
Hello {{person.name}},
|
||||
<div>
|
||||
Your contact information:
|
||||
<div ng:repeat="contact in person.contacts">{{contact.type}}: {{contact.url|linky}}</div>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
@ngdoc overview
|
||||
@name Testimonials
|
||||
@description
|
||||
|
||||
|
||||
## John Hardy
|
||||
> Also I want to pass on my compliments to Misko and Igor for this fantastic project. I'm currently
|
||||
> rewriting a server-side web application to use this system. I am constantly astounded at how much
|
||||
> simpler it is to do it this way and I still consider myself a learner.
|
||||
|
||||
> This is without question the most productive approach to building webapps that I have seen.
|
||||
|
||||
> The last time I had a coding epiphany was discovering the power and simplicity of JQuery. This is
|
||||
> way better than that.
|
||||
|
||||
> I'm interested in promoting this library as widely as possible. I understand that you are still
|
||||
> developing it and I still have a long way to go before I really understand everything but I think
|
||||
> you really have something here.
|
||||
|
||||
|
||||
## Jerry Jeremiah
|
||||
> angular is the best thing I have used in a long time. I am having so much fun, even thought it is
|
||||
> probably obvious that dynamic web sites are new to me (my experience is more in the back end
|
||||
> embedded world...)
|
||||
|
||||
|
||||
## Dobrica Pavlinusic
|
||||
> Thanks to great help I received at this list, I was basically able to accomplish my goal to write
|
||||
> simple conference submission application within a week of first git clone of angular source from
|
||||
> github.
|
||||
|
||||
> I think it might be useful to summarize my experience here, especially for people who are still
|
||||
> wondering if angular is worth a try. Executive summary is: **yes it is!**
|
||||
@@ -0,0 +1,2 @@
|
||||
view1<br>
|
||||
location: {{$service('$location').href}}
|
||||
@@ -0,0 +1,2 @@
|
||||
view2<br/>
|
||||
location: {{$service('$location').href}}<br/>
|
||||
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var http = require('http');
|
||||
var https = require('https');
|
||||
var fs = require('fs');
|
||||
|
||||
var collections = {
|
||||
'guide': 'http://docs.google.com/feeds/default/private/full/folder%3A0B9PsajIPqzmANGUwMGVhZmYtMTk1ZC00NTdmLWIxMDAtZGI5YWNlZjQ2YjZl/contents',
|
||||
'api': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDYjMwYTc2YWUtZTgzYy00YjIxLThlZDYtYWJlOTFlNzE2NzEw/contents',
|
||||
'cookbook': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDNzkxZWM5ZTItN2M5NC00NWIxLTg2ZDMtMmYwNDY1NWM1MGU4/contents',
|
||||
'misc': 'http://docs.google.com/feeds/default/private/full/folder%3A0B7Ovm8bUYiUDZjVlNmZkYzQtMjZlOC00NmZhLWI5MjAtMGRjZjlkOGJkMDBi/contents'
|
||||
}
|
||||
|
||||
console.log('Google Docs...');
|
||||
|
||||
var flag = process && process.argv[2];
|
||||
if (flag == '--login')
|
||||
askPassword(function(password){
|
||||
login(process.argv[3], password);
|
||||
});
|
||||
else if (flag == '--fetch') {
|
||||
var collection = process.argv[3];
|
||||
if (collection) {
|
||||
fetch(collection, collections[collection]);
|
||||
} else {
|
||||
for (collection in collections)
|
||||
fetch(collection, collections[collection]);
|
||||
}
|
||||
} else
|
||||
help();
|
||||
|
||||
function help(){
|
||||
console.log('Synopsys');
|
||||
console.log('gdocs.js --login <username>');
|
||||
console.log('gdocs.js --fetch [<docs collection>]');
|
||||
process.exit(-1);
|
||||
};
|
||||
|
||||
|
||||
function fetch(name, url){
|
||||
//https://docs.google.com/feeds/default/private/full/folder%3Afolder_id/contents
|
||||
console.log('fetching a list of docs in collection ' + name + '...');
|
||||
request('GET', url, {
|
||||
headers: {
|
||||
'Gdata-Version': '3.0',
|
||||
'Authorization': 'GoogleLogin auth=' + getAuthToken()
|
||||
}
|
||||
},
|
||||
function(chunk){
|
||||
var entries = chunk.split('<entry');
|
||||
entries.shift();
|
||||
entries.forEach(function(entry){
|
||||
var title = entry.match(/<title>(.*?)<\/title>/)[1];
|
||||
if (title.match(/\.ngdoc$/)) {
|
||||
var exportUrl = entry.match(/<content type='text\/html' src='(.*?)'\/>/)[1];
|
||||
download(title, exportUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function download(name, url) {
|
||||
console.log('Downloading:', name, '...');
|
||||
request('GET', url + '&exportFormat=txt',
|
||||
{
|
||||
headers: {
|
||||
'Gdata-Version': '3.0',
|
||||
'Authorization': 'GoogleLogin auth=' + getAuthToken()
|
||||
}
|
||||
},
|
||||
function(data){
|
||||
data = data.replace('\ufeff', '');
|
||||
data = data.replace(/\r\n/mg, '\n');
|
||||
data = data.replace(/^ /mg, ' '); //for some reason gdocs drop first space for indented lines
|
||||
|
||||
// strip out all text annotation comments
|
||||
data = data.replace(/^\[a\][\S\s]*/m, '');
|
||||
|
||||
// strip out all text annotations
|
||||
data = data.replace(/\[\w{1,3}\]/mg, '');
|
||||
|
||||
// fix smart-quotes
|
||||
data = data.replace(/[“”]/g, '"');
|
||||
data = data.replace(/[‘’]/g, "'");
|
||||
|
||||
|
||||
data = data + '\n';
|
||||
fs.writeFileSync('docs/' + name, reflow(data, 100));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* token=$(curl
|
||||
* -s https://www.google.com/accounts/ClientLogin
|
||||
* -d Email=...username...
|
||||
* -d Passwd=...password...
|
||||
* -d accountType=GOOGLE
|
||||
* -d service=writely
|
||||
* -d Gdata-version=3.0 | cut -d "=" -f 2)
|
||||
*/
|
||||
function login(username, password){
|
||||
request('POST', 'https://www.google.com/accounts/ClientLogin',
|
||||
{
|
||||
data: {
|
||||
Email: username,
|
||||
Passwd: password,
|
||||
accountType: 'GOOGLE',
|
||||
service: 'writely',
|
||||
'Gdata-version': '3.0'
|
||||
},
|
||||
headers: {
|
||||
'Content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
},
|
||||
function(chunk){
|
||||
var token;
|
||||
chunk.split('\n').forEach(function(line){
|
||||
var parts = line.split('=');
|
||||
if (parts[0] == 'Auth') {
|
||||
token = parts[1];
|
||||
}
|
||||
});
|
||||
if (token) {
|
||||
fs.writeFileSync('tmp/gdocs.auth', token);
|
||||
console.log("logged in, token saved in 'tmp/gdocs.auth'");
|
||||
} else {
|
||||
console.log('failed to log in');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getAuthToken(){
|
||||
return fs.readFileSync('tmp/gdocs.auth');
|
||||
}
|
||||
|
||||
function request(method, url, options, response) {
|
||||
var url = url.match(/http(s?):\/\/(.+?)(\/.*)/);
|
||||
var request = (url[1] ? https : http).request({
|
||||
host: url[2],
|
||||
port: (url[1] ? 443 : 80),
|
||||
path: url[3],
|
||||
method: method
|
||||
}, function(res){
|
||||
var data = [];
|
||||
res.setEncoding('utf8');
|
||||
res.on('end', function(){
|
||||
response(data.join(''));
|
||||
});
|
||||
res.on('data', function (chunk) {
|
||||
data.push(chunk);
|
||||
});
|
||||
});
|
||||
for(var header in options.headers) {
|
||||
request.setHeader(header, options.headers[header]);
|
||||
}
|
||||
if (options.data)
|
||||
request.write(encodeData(options.data));
|
||||
request.on('end', function(){
|
||||
console.log('end');
|
||||
});
|
||||
request.end();
|
||||
}
|
||||
|
||||
function encodeData(obj) {
|
||||
var pairs = [];
|
||||
for(var key in obj) {
|
||||
pairs.push(key + '=' + obj[key]);
|
||||
}
|
||||
return pairs.join('&') + '\n';
|
||||
}
|
||||
|
||||
function askPassword(callback) {
|
||||
var stdin = process.openStdin(),
|
||||
stdio = process.binding("stdio");
|
||||
|
||||
stdio.setRawMode();
|
||||
|
||||
console.log('Enter your password:');
|
||||
var password = ""
|
||||
stdin.on("data", function (c) {
|
||||
c = c + "";
|
||||
switch (c) {
|
||||
case "\n": case "\r": case "\u0004":
|
||||
stdio.setRawMode(false);
|
||||
stdin.pause();
|
||||
callback(password);
|
||||
break;
|
||||
case "\u0003":
|
||||
process.exit();
|
||||
break;
|
||||
default:
|
||||
password += c;
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function reflow(text, margin) {
|
||||
var lines = [];
|
||||
text.split(/\n/).forEach(function(line) {
|
||||
var col = 0;
|
||||
var reflowLine = '';
|
||||
function flush(){
|
||||
reflowLine = reflowLine.replace(/\s*$/, '');
|
||||
lines.push(reflowLine);
|
||||
reflowLine = '';
|
||||
col = 0;
|
||||
}
|
||||
line.replace(/\s*\S*\s*/g, function(chunk){
|
||||
if (col + chunk.length > margin) flush();
|
||||
reflowLine += chunk;
|
||||
col += chunk.length;
|
||||
});
|
||||
flush();
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 667 B |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -8,6 +8,7 @@ load:
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- src/service/*.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
@@ -18,6 +19,7 @@ load:
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
- test/service/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
|
||||
@@ -8,6 +8,7 @@ load:
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- src/service/*.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
@@ -18,6 +19,7 @@ load:
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
- test/service/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
|
||||
@@ -8,6 +8,7 @@ load:
|
||||
- src/Angular.js
|
||||
- src/JSON.js
|
||||
- src/*.js
|
||||
- src/service/*.js
|
||||
- example/personalLog/*.js
|
||||
- test/testabilityPatch.js
|
||||
- src/scenario/Scenario.js
|
||||
@@ -18,6 +19,7 @@ load:
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
- test/service/*.js
|
||||
- example/personalLog/test/*.js
|
||||
|
||||
exclude:
|
||||
|
||||
@@ -122,7 +122,9 @@
|
||||
# or "+process Folder\Path\*.htm".
|
||||
#
|
||||
+process src/*.js
|
||||
+process src/service/*.js
|
||||
+process src/scenario/*.js
|
||||
+process test/*.js
|
||||
+process test/service/*.js
|
||||
+process test/scenario/*.js
|
||||
|
||||
|
||||
@@ -30,12 +30,14 @@ var uppercase = function (string){ return isString(string) ? string.toUpperCase(
|
||||
|
||||
|
||||
var manualLowercase = function (s) {
|
||||
return isString(s) ? s.replace(/[A-Z]/g,
|
||||
function (ch) {return fromCharCode(ch.charCodeAt(0) | 32); }) : s;
|
||||
return isString(s)
|
||||
? s.replace(/[A-Z]/g, function (ch) {return fromCharCode(ch.charCodeAt(0) | 32); })
|
||||
: s;
|
||||
};
|
||||
var manualUppercase = function (s) {
|
||||
return isString(s) ? s.replace(/[a-z]/g,
|
||||
function (ch) {return fromCharCode(ch.charCodeAt(0) & ~32); }) : s;
|
||||
return isString(s)
|
||||
? s.replace(/[a-z]/g, function (ch) {return fromCharCode(ch.charCodeAt(0) & ~32); })
|
||||
: s;
|
||||
};
|
||||
|
||||
|
||||
@@ -83,18 +85,21 @@ var _undefined = undefined,
|
||||
PRIORITY_LAST = 99999,
|
||||
PRIORITY = {'FIRST': PRIORITY_FIRST, 'LAST': PRIORITY_LAST, 'WATCH':PRIORITY_WATCH},
|
||||
Error = window.Error,
|
||||
jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
|
||||
_ = window['_'],
|
||||
/** holds major version number for IE or NaN for real browsers */
|
||||
msie = parseInt((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1], 10),
|
||||
jqLite = jQuery || jqLiteWrap,
|
||||
jqLite, // delay binding since jQuery could be loaded after us.
|
||||
jQuery, // delay binding
|
||||
slice = Array.prototype.slice,
|
||||
push = Array.prototype.push,
|
||||
error = window[$console] ? bind(window[$console], window[$console]['error'] || noop) : noop,
|
||||
error = window[$console]
|
||||
? bind(window[$console], window[$console]['error'] || noop)
|
||||
: noop,
|
||||
|
||||
/** @name angular */
|
||||
angular = window[$angular] || (window[$angular] = {}),
|
||||
angular = window[$angular] || (window[$angular] = {}),
|
||||
/** @name angular.markup */
|
||||
angularTextMarkup = extensionMap(angular, 'markup'),
|
||||
/** @name angular.attrMarkup */
|
||||
angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
|
||||
/** @name angular.directive */
|
||||
angularDirective = extensionMap(angular, 'directive'),
|
||||
@@ -193,7 +198,7 @@ function formatError(arg) {
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Extends the destination object `dst` by copying all of the properties from the `src` objects to
|
||||
* Extends the destination object `dst` by copying all of the properties from the `src` object(s) to
|
||||
* `dst`. You can specify multiple `src` objects.
|
||||
*
|
||||
* @param {Object} dst The destination object.
|
||||
@@ -267,21 +272,6 @@ function extensionMap(angular, name, transform) {
|
||||
});
|
||||
}
|
||||
|
||||
function jqLiteWrap(element) {
|
||||
// for some reasons the parentNode of an orphan looks like _null but its typeof is object.
|
||||
if (element) {
|
||||
if (isString(element)) {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = element;
|
||||
element = new JQLite(div.childNodes);
|
||||
} else if (!(element instanceof JQLite)) {
|
||||
element = new JQLite(element);
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
@@ -418,7 +408,9 @@ function isBoolean(value) { return typeof value == $boolean;}
|
||||
function isTextNode(node) { return nodeName_(node) == '#text'; }
|
||||
function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
|
||||
function isElement(node) {
|
||||
return node && (node.nodeName || node instanceof JQLite || (jQuery && node instanceof jQuery));
|
||||
return node &&
|
||||
(node.nodeName // we are a direct element
|
||||
|| (node.bind && node.find)); // we have a bind and find method part of jQuery API
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -429,13 +421,13 @@ function isElement(node) {
|
||||
*/
|
||||
function HTML(html, option) {
|
||||
this.html = html;
|
||||
this.get = lowercase(option) == 'unsafe' ?
|
||||
valueFn(html) :
|
||||
function htmlSanitize() {
|
||||
var buf = [];
|
||||
htmlParser(html, htmlSanitizeWriter(buf));
|
||||
return buf.join('');
|
||||
};
|
||||
this.get = lowercase(option) == 'unsafe'
|
||||
? valueFn(html)
|
||||
: function htmlSanitize() {
|
||||
var buf = [];
|
||||
htmlParser(html, htmlSanitizeWriter(buf));
|
||||
return buf.join('');
|
||||
};
|
||||
}
|
||||
|
||||
if (msie) {
|
||||
@@ -449,10 +441,6 @@ if (msie) {
|
||||
};
|
||||
}
|
||||
|
||||
function quickClone(element) {
|
||||
return jqLite(element[0].cloneNode(true));
|
||||
}
|
||||
|
||||
function isVisible(element) {
|
||||
var rect = element[0].getBoundingClientRect(),
|
||||
width = (rect.width || (rect.right||0 - rect.left||0)),
|
||||
@@ -484,14 +472,18 @@ function map(obj, iterator, context) {
|
||||
* @returns {number} The size of `obj` or `0` if `obj` is neither an object or an array.
|
||||
*
|
||||
* @example
|
||||
* Number of items in array: {{ [1,2].$size() }}<br/>
|
||||
* Number of items in object: {{ {a:1, b:2, c:3}.$size() }}<br/>
|
||||
*
|
||||
* @scenario
|
||||
it('should print correct sizes for an array and an object', function() {
|
||||
expect(binding('[1,2].$size()')).toBe('2');
|
||||
expect(binding('{a:1, b:2, c:3}.$size()')).toBe('3');
|
||||
});
|
||||
* <doc:example>
|
||||
* <doc:source>
|
||||
* Number of items in array: {{ [1,2].$size() }}<br/>
|
||||
* Number of items in object: {{ {a:1, b:2, c:3}.$size() }}<br/>
|
||||
* </doc:source>
|
||||
* <doc:scenario>
|
||||
* it('should print correct sizes for an array and an object', function() {
|
||||
* expect(binding('[1,2].$size()')).toBe('2');
|
||||
* expect(binding('{a:1, b:2, c:3}.$size()')).toBe('3');
|
||||
* });
|
||||
* </doc:scenario>
|
||||
* </doc:example>
|
||||
*/
|
||||
function size(obj) {
|
||||
var size = 0, key;
|
||||
@@ -556,27 +548,31 @@ function isLeafNode (node) {
|
||||
* @returns {*} The copy or updated `destination` if `destination` was specified.
|
||||
*
|
||||
* @example
|
||||
Salutation: <input type="text" name="master.salutation" value="Hello" /><br/>
|
||||
Name: <input type="text" name="master.name" value="world"/><br/>
|
||||
<button ng:click="form = master.$copy()">copy</button>
|
||||
<hr/>
|
||||
* <doc:example>
|
||||
* <doc:source>
|
||||
Salutation: <input type="text" name="master.salutation" value="Hello" /><br/>
|
||||
Name: <input type="text" name="master.name" value="world"/><br/>
|
||||
<button ng:click="form = master.$copy()">copy</button>
|
||||
<hr/>
|
||||
|
||||
The master object is <span ng:hide="master.$equals(form)">NOT</span> equal to the form object.
|
||||
The master object is <span ng:hide="master.$equals(form)">NOT</span> equal to the form object.
|
||||
|
||||
<pre>master={{master}}</pre>
|
||||
<pre>form={{form}}</pre>
|
||||
|
||||
* @scenario
|
||||
<pre>master={{master}}</pre>
|
||||
<pre>form={{form}}</pre>
|
||||
* </doc:source>
|
||||
* <doc:scenario>
|
||||
it('should print that initialy the form object is NOT equal to master', function() {
|
||||
expect(element('.doc-example input[name=master.salutation]').val()).toBe('Hello');
|
||||
expect(element('.doc-example input[name=master.name]').val()).toBe('world');
|
||||
expect(element('.doc-example span').css('display')).toBe('inline');
|
||||
expect(element('.doc-example-live input[name=master.salutation]').val()).toBe('Hello');
|
||||
expect(element('.doc-example-live input[name=master.name]').val()).toBe('world');
|
||||
expect(element('.doc-example-live span').css('display')).toBe('inline');
|
||||
});
|
||||
|
||||
it('should make form and master equal when the copy button is clicked', function() {
|
||||
element('.doc-example button').click();
|
||||
expect(element('.doc-example span').css('display')).toBe('none');
|
||||
element('.doc-example-live button').click();
|
||||
expect(element('.doc-example-live span').css('display')).toBe('none');
|
||||
});
|
||||
* </doc:scenario>
|
||||
* </doc:example>
|
||||
*/
|
||||
function copy(source, destination){
|
||||
if (!destination) {
|
||||
@@ -633,27 +629,31 @@ function copy(source, destination){
|
||||
* @returns {boolean} True if arguments are equal.
|
||||
*
|
||||
* @example
|
||||
Salutation: <input type="text" name="greeting.salutation" value="Hello" /><br/>
|
||||
Name: <input type="text" name="greeting.name" value="world"/><br/>
|
||||
<hr/>
|
||||
* <doc:example>
|
||||
* <doc:source>
|
||||
Salutation: <input type="text" name="greeting.salutation" value="Hello" /><br/>
|
||||
Name: <input type="text" name="greeting.name" value="world"/><br/>
|
||||
<hr/>
|
||||
|
||||
The <code>greeting</code> object is
|
||||
<span ng:hide="greeting.$equals({salutation:'Hello', name:'world'})">NOT</span> equal to
|
||||
<code>{salutation:'Hello', name:'world'}</code>.
|
||||
The <code>greeting</code> object is
|
||||
<span ng:hide="greeting.$equals({salutation:'Hello', name:'world'})">NOT</span> equal to
|
||||
<code>{salutation:'Hello', name:'world'}</code>.
|
||||
|
||||
<pre>greeting={{greeting}}</pre>
|
||||
<pre>greeting={{greeting}}</pre>
|
||||
* </doc:source>
|
||||
* <doc:scenario>
|
||||
it('should print that initialy greeting is equal to the hardcoded value object', function() {
|
||||
expect(element('.doc-example-live input[name=greeting.salutation]').val()).toBe('Hello');
|
||||
expect(element('.doc-example-live input[name=greeting.name]').val()).toBe('world');
|
||||
expect(element('.doc-example-live span').css('display')).toBe('none');
|
||||
});
|
||||
|
||||
@scenario
|
||||
it('should print that initialy greeting is equal to the hardcoded value object', function() {
|
||||
expect(element('.doc-example input[name=greeting.salutation]').val()).toBe('Hello');
|
||||
expect(element('.doc-example input[name=greeting.name]').val()).toBe('world');
|
||||
expect(element('.doc-example span').css('display')).toBe('none');
|
||||
});
|
||||
|
||||
it('should say that the objects are not equal when the form is modified', function() {
|
||||
input('greeting.name').enter('kitty');
|
||||
expect(element('.doc-example span').css('display')).toBe('inline');
|
||||
});
|
||||
it('should say that the objects are not equal when the form is modified', function() {
|
||||
input('greeting.name').enter('kitty');
|
||||
expect(element('.doc-example-live span').css('display')).toBe('inline');
|
||||
});
|
||||
* </doc:scenario>
|
||||
* </doc:example>
|
||||
*/
|
||||
function equals(o1, o2) {
|
||||
if (o1 == o2) return true;
|
||||
@@ -707,7 +707,7 @@ function elementError(element, type, error) {
|
||||
element[0]['$NG_ERROR'] = error;
|
||||
if (error) {
|
||||
element.addClass(type);
|
||||
element.attr(type, error);
|
||||
element.attr(type, error.message || error);
|
||||
} else {
|
||||
element.removeClass(type);
|
||||
element.removeAttr(type);
|
||||
@@ -727,11 +727,11 @@ function concat(array1, array2, index) {
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Returns function which calls function `fn` bound to `self` (`self` becomes the `this` for `fn`).
|
||||
* Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for `fn`).
|
||||
* Optional `args` can be supplied which are prebound to the function, also known as
|
||||
* [function currying](http://en.wikipedia.org/wiki/Currying).
|
||||
*
|
||||
* @param {Object} self Context in which `fn` should be evaluated in.
|
||||
* @param {Object} self Context which `fn` should be evaluated in.
|
||||
* @param {function()} fn Function to be bound.
|
||||
* @param {...*} args Optional arguments to be prebound to the `fn` function call.
|
||||
* @returns {function()} Function that wraps the `fn` with all the specified bindings.
|
||||
@@ -774,30 +774,10 @@ function merge(src, dst) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.compile
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Compiles a piece of HTML or DOM into a {@link angular.scope scope} object.
|
||||
<pre>
|
||||
var scope1 = angular.compile(window.document);
|
||||
scope1.$init();
|
||||
|
||||
var scope2 = angular.compile('<div ng:click="clicked = true">click me</div>');
|
||||
scope2.$init();
|
||||
</pre>
|
||||
*
|
||||
* @param {string|DOMElement} element Element to compile.
|
||||
* @param {Object=} parentScope Scope to become the parent scope of the newly compiled scope.
|
||||
* @returns {Object} Compiled scope object.
|
||||
*/
|
||||
function compile(element, parentScope) {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget),
|
||||
$element = jqLite(element);
|
||||
return compiler.compile($element)($element, parentScope);
|
||||
/** @name angular.compile */
|
||||
function compile(element) {
|
||||
return new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget)
|
||||
.compile(element);
|
||||
}
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
@@ -825,6 +805,23 @@ function toKeyValue(obj) {
|
||||
return parts.length ? parts.join('&') : '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* we need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
|
||||
* http://www.ietf.org/rfc/rfc2396.txt with regards to the character set (pchar) allowed in path
|
||||
* segments
|
||||
*/
|
||||
function encodeUriSegment(val) {
|
||||
return encodeURIComponent(val).
|
||||
replace(/%40/gi, '@').
|
||||
replace(/%3A/gi, ':').
|
||||
replace(/%26/gi, '&').
|
||||
replace(/%3D/gi, '=').
|
||||
replace(/%2B/gi, '+').
|
||||
replace(/%24/g, '$').
|
||||
replace(/%2C/gi, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
@@ -836,8 +833,8 @@ function toKeyValue(obj) {
|
||||
* @TODO rename to ng:autobind to ng:autoboot
|
||||
*
|
||||
* @description
|
||||
* This section explains how to bootstrap your application with angular using either the angular
|
||||
* javascript file.
|
||||
* This section explains how to bootstrap your application with angular, using either the angular
|
||||
* javascript file, or manually.
|
||||
*
|
||||
*
|
||||
* ## The angular distribution
|
||||
@@ -932,7 +929,7 @@ function toKeyValue(obj) {
|
||||
(function(window, previousOnLoad){
|
||||
window.onload = function(){
|
||||
try { (previousOnLoad||angular.noop)(); } catch(e) {}
|
||||
angular.compile(window.document).$init();
|
||||
angular.compile(window.document);
|
||||
};
|
||||
})(window, window.onload);
|
||||
</script>
|
||||
@@ -979,19 +976,18 @@ function toKeyValue(obj) {
|
||||
function angularInit(config){
|
||||
if (config.autobind) {
|
||||
// TODO default to the source of angular.js
|
||||
var scope = compile(window.document, _null, {'$config':config}),
|
||||
var scope = compile(window.document)(createScope({'$config':config})),
|
||||
$browser = scope.$service('$browser');
|
||||
|
||||
if (config.css)
|
||||
$browser.addCss(config.base_url + config.css);
|
||||
else if(msie<8)
|
||||
$browser.addJs(config.base_url + config.ie_compat, config.ie_compat_id);
|
||||
|
||||
scope.$init();
|
||||
}
|
||||
}
|
||||
|
||||
function angularJsConfig(document, config) {
|
||||
bindJQuery();
|
||||
var scripts = document.getElementsByTagName("script"),
|
||||
match;
|
||||
config = extend({
|
||||
@@ -1014,3 +1010,34 @@ function angularJsConfig(document, config) {
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
function bindJQuery(){
|
||||
// bind to jQuery if present;
|
||||
jQuery = window.jQuery;
|
||||
// reset to jQuery or default to us.
|
||||
if (jQuery) {
|
||||
jqLite = jQuery;
|
||||
extend(jQuery.fn, {
|
||||
scope: JQLitePrototype.scope
|
||||
});
|
||||
} else {
|
||||
jqLite = jqLiteWrap;
|
||||
}
|
||||
angular.element = jqLite;
|
||||
}
|
||||
|
||||
/**
|
||||
* throw error of the argument is falsy.
|
||||
*/
|
||||
function assertArg(arg, name, reason) {
|
||||
if (!arg) {
|
||||
var error = new Error("Argument '" + (name||'?') + "' is " +
|
||||
(reason || "required"));
|
||||
if (window.console) window.console.log(error.stack);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
function assertArgFn(arg, name) {
|
||||
assertArg(isFunction(arg, name, 'not a function'));
|
||||
};
|
||||
|
||||
@@ -46,3 +46,8 @@ extend(angular, {
|
||||
'isArray': isArray
|
||||
});
|
||||
|
||||
//try to bind to jquery now so that one can write angular.element().read()
|
||||
//but we will rebind on bootstrap again.
|
||||
bindJQuery();
|
||||
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ function Browser(window, document, body, XHR, $log) {
|
||||
callback = post;
|
||||
post = _null;
|
||||
}
|
||||
outstandingRequestCount ++;
|
||||
if (lowercase(method) == 'json') {
|
||||
var callbackId = "angular_" + Math.random() + '_' + (idCounter++);
|
||||
callbackId = callbackId.replace(/\d\./, '');
|
||||
@@ -89,7 +90,7 @@ function Browser(window, document, body, XHR, $log) {
|
||||
script.src = url.replace('JSON_CALLBACK', callbackId);
|
||||
window[callbackId] = function(data){
|
||||
window[callbackId] = _undefined;
|
||||
callback(200, data);
|
||||
completeOutstandingRequest(callback, 200, data);
|
||||
};
|
||||
body.append(script);
|
||||
} else {
|
||||
@@ -98,7 +99,6 @@ function Browser(window, document, body, XHR, $log) {
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.setRequestHeader("Accept", "application/json, text/plain, */*");
|
||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||
outstandingRequestCount ++;
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
completeOutstandingRequest(callback, xhr.status || 200, xhr.responseText);
|
||||
@@ -228,7 +228,7 @@ function Browser(window, document, body, XHR, $log) {
|
||||
* The listener gets called with either HashChangeEvent object or simple object that also contains
|
||||
* `oldURL` and `newURL` properties.
|
||||
*
|
||||
* NOTE: this is a api is intended for sole use by $location service. Please use
|
||||
* NOTE: this api is intended for use only by the $location service. Please use the
|
||||
* {@link angular.service.$location $location service} to monitor hash changes in angular apps.
|
||||
*
|
||||
* @param {function(event)} listener Listener function to be called when url hash changes.
|
||||
@@ -243,11 +243,12 @@ function Browser(window, document, body, XHR, $log) {
|
||||
self.addPollFn(function() {
|
||||
if (lastBrowserUrl != self.getUrl()) {
|
||||
listener();
|
||||
lastBrowserUrl = self.getUrl();
|
||||
}
|
||||
});
|
||||
}
|
||||
return listener;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// Cookies API
|
||||
@@ -323,13 +324,13 @@ function Browser(window, document, body, XHR, $log) {
|
||||
* @name angular.service.$browser#defer
|
||||
* @methodOf angular.service.$browser
|
||||
* @param {function()} fn A function, who's execution should be defered.
|
||||
* @param {int=} [delay=0] of milliseconds to defer the function execution.
|
||||
* @param {number=} [delay=0] of milliseconds to defer the function execution.
|
||||
*
|
||||
* @description
|
||||
* Executes a fn asynchroniously via `setTimeout(fn, delay)`.
|
||||
*
|
||||
* Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
|
||||
* `setTimeout` in tests, the fns are queued in an array, which can be programaticaly flushed via
|
||||
* `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed via
|
||||
* `$browser.defer.flush()`.
|
||||
*
|
||||
*/
|
||||
@@ -352,7 +353,7 @@ function Browser(window, document, body, XHR, $log) {
|
||||
* @description
|
||||
* Set hover listener.
|
||||
*
|
||||
* @param {function(Object, boolean)} listener Function that will be called when hover event
|
||||
* @param {function(Object, boolean)} listener Function that will be called when a hover event
|
||||
* occurs.
|
||||
*/
|
||||
self.hover = function(listener) { hoverListener = listener; };
|
||||
|
||||
@@ -13,7 +13,7 @@ function Template(priority) {
|
||||
}
|
||||
|
||||
Template.prototype = {
|
||||
init: function(element, scope) {
|
||||
attach: function(element, scope) {
|
||||
var inits = {};
|
||||
this.collectInits(element, inits, scope);
|
||||
forEachSorted(inits, function(queue){
|
||||
@@ -26,7 +26,6 @@ Template.prototype = {
|
||||
if (!queue) {
|
||||
inits[this.priority] = queue = [];
|
||||
}
|
||||
element = jqLite(element);
|
||||
if (this.newScope) {
|
||||
childScope = createScope(scope);
|
||||
scope.$onEval(childScope.$eval);
|
||||
@@ -45,7 +44,7 @@ Template.prototype = {
|
||||
paths = this.paths,
|
||||
length = paths.length;
|
||||
for (i = 0; i < length; i++) {
|
||||
children[i].collectInits(childNodes[paths[i]], inits, childScope);
|
||||
children[i].collectInits(jqLite(childNodes[paths[i]]), inits, childScope);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -69,21 +68,87 @@ Template.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Function walks up the element chain looking for the scope associated with the give element.
|
||||
*/
|
||||
function retrieveScope(element) {
|
||||
var scope;
|
||||
element = jqLite(element);
|
||||
while (element && element.length && !(scope = element.data($$scope))) {
|
||||
element = element.parent();
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
//Compiler
|
||||
//////////////////////////////////
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc function
|
||||
* @name angular.compile
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
|
||||
* can then be used to link {@link angular.scope scope} and the template together.
|
||||
*
|
||||
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
|
||||
* {@link angular.markup markup}, {@link angular.attrMarkup attrMarkup},
|
||||
* {@link angular.widget widgets}, and {@link angular.directive directives}. For each match it
|
||||
* executes coresponding markup, attrMarkup, widget or directive template function and collects the
|
||||
* instance functions into a single template function which is then returned.
|
||||
*
|
||||
* The template function can then be used once to produce the view or as it is the case with
|
||||
* {@link angular.widget.@ng:repeat repeater} many-times, in which case each call results in a view
|
||||
* that is a DOM clone of the original template.
|
||||
*
|
||||
<pre>
|
||||
//copile the entire window.document and give me the scope bound to this template.
|
||||
var rootSscope = angular.compile(window.document)();
|
||||
|
||||
//compile a piece of html
|
||||
var rootScope2 = angular.compile(''<div ng:click="clicked = true">click me</div>')();
|
||||
|
||||
//compile a piece of html and retain reference to both the dom and scope
|
||||
var template = angular.element('<div ng:click="clicked = true">click me</div>'),
|
||||
scoope = angular.compile(view)();
|
||||
//at this point template was transformed into a view
|
||||
</pre>
|
||||
*
|
||||
*
|
||||
* @param {string|DOMElement} element Element or HTML to compile into a template function.
|
||||
* @returns {function([scope][, cloneAttachFn])} a template function which is used to bind template
|
||||
* (a DOM element/tree) to a scope. Where:
|
||||
*
|
||||
* * `scope` - A {@link angular.scope scope} to bind to. If none specified, then a new
|
||||
* root scope is created.
|
||||
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
|
||||
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
|
||||
* cloned elements to the DOM document at the approriate place. The `cloneAttachFn` is
|
||||
* called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
|
||||
*
|
||||
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
|
||||
* * `scope` - is the current scope with which the linking function is working with.
|
||||
*
|
||||
* Calling the template function returns the scope to which the element is bound to. It is either
|
||||
* the same scope as the one passed into the template function, or if none were provided it's the
|
||||
* newly create scope.
|
||||
*
|
||||
* If you need access to the bound view, there are two ways to do it:
|
||||
*
|
||||
* - If you are not asking the linking function to clone the template, create the DOM element(s)
|
||||
* before you send them to the compiler and keep this reference around.
|
||||
* <pre>
|
||||
* var view = angular.element('<p>{{total}}</p>'),
|
||||
* scope = angular.compile(view)();
|
||||
* </pre>
|
||||
*
|
||||
* - if on the other hand, you need the element to be cloned, the view reference from the original
|
||||
* example would not point to the clone, but rather to the original template that was cloned. In
|
||||
* this case, you can access the clone via the cloneAttachFn:
|
||||
* <pre>
|
||||
* var original = angular.element('<p>{{total}}</p>'),
|
||||
* scope = someParentScope.$new(),
|
||||
* clone;
|
||||
*
|
||||
* angular.compile(original)(scope, function(clonedElement, scope) {
|
||||
* clone = clonedElement;
|
||||
* //attach the clone to DOM document at the right place
|
||||
* });
|
||||
*
|
||||
* //now we have reference to the cloned DOM via `clone`
|
||||
* </pre>
|
||||
*/
|
||||
function Compiler(markup, attrMarkup, directives, widgets){
|
||||
this.markup = markup;
|
||||
this.attrMarkup = attrMarkup;
|
||||
@@ -92,34 +157,33 @@ function Compiler(markup, attrMarkup, directives, widgets){
|
||||
}
|
||||
|
||||
Compiler.prototype = {
|
||||
compile: function(element) {
|
||||
element = jqLite(element);
|
||||
compile: function(templateElement) {
|
||||
templateElement = jqLite(templateElement);
|
||||
var index = 0,
|
||||
template,
|
||||
parent = element.parent();
|
||||
parent = templateElement.parent();
|
||||
if (parent && parent[0]) {
|
||||
parent = parent[0];
|
||||
for(var i = 0; i < parent.childNodes.length; i++) {
|
||||
if (parent.childNodes[i] == element[0]) {
|
||||
if (parent.childNodes[i] == templateElement[0]) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
template = this.templatize(element, index, 0) || new Template();
|
||||
return function(element, parentScope){
|
||||
element = jqLite(element);
|
||||
var scope = parentScope && parentScope.$eval ?
|
||||
parentScope : createScope(parentScope);
|
||||
template = this.templatize(templateElement, index, 0) || new Template();
|
||||
return function(scope, cloneConnectFn){
|
||||
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
|
||||
// and sometimes changes the structure of the DOM.
|
||||
var element = cloneConnectFn
|
||||
? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!!
|
||||
: templateElement;
|
||||
scope = scope || createScope();
|
||||
element.data($$scope, scope);
|
||||
return extend(scope, {
|
||||
$element:element,
|
||||
$init: function() {
|
||||
template.init(element, scope);
|
||||
scope.$eval();
|
||||
delete scope.$init;
|
||||
return scope;
|
||||
}
|
||||
});
|
||||
scope.$element = element;
|
||||
(cloneConnectFn||noop)(element, scope);
|
||||
template.attach(element, scope);
|
||||
scope.$eval();
|
||||
return scope;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -139,40 +203,44 @@ Compiler.prototype = {
|
||||
* @element ANY
|
||||
* @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
* try changing the invoice and see that the Total will lag in evaluation
|
||||
* @example
|
||||
<div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]">
|
||||
<tr>
|
||||
<td>QTY</td>
|
||||
<td>Description</td>
|
||||
<td>Cost</td>
|
||||
<td>Total</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr ng:repeat="item in items">
|
||||
<td><input name="item.qty"/></td>
|
||||
<td><input name="item.desc"/></td>
|
||||
<td><input name="item.cost"/></td>
|
||||
<td>{{item.total = item.qty * item.cost | currency}}</td>
|
||||
<td><a href="" ng:click="items.$remove(item)">X</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><a href="" ng:click="items.$add()">add</a></td>
|
||||
<td>{{ items.$sum('total') | currency }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:format', function(){
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99');
|
||||
input('item.qty').enter('2');
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98');
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div>
|
||||
<table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]">
|
||||
<tr>
|
||||
<td>QTY</td>
|
||||
<td>Description</td>
|
||||
<td>Cost</td>
|
||||
<td>Total</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr ng:repeat="item in items">
|
||||
<td><input name="item.qty"/></td>
|
||||
<td><input name="item.desc"/></td>
|
||||
<td><input name="item.cost"/></td>
|
||||
<td>{{item.total = item.qty * item.cost | currency}}</td>
|
||||
<td><a href="" ng:click="items.$remove(item)">X</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3"><a href="" ng:click="items.$add()">add</a></td>
|
||||
<td>{{ items.$sum('total') | currency }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:format', function(){
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99');
|
||||
input('item.qty').enter('2');
|
||||
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
|
||||
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
|
||||
templatize: function(element, elementIndex, priority){
|
||||
@@ -183,12 +251,10 @@ Compiler.prototype = {
|
||||
descend = true,
|
||||
directives = true,
|
||||
elementName = nodeName_(element),
|
||||
elementNamespace = elementName.indexOf(':') > 0 ? lowercase(elementName).replace(':', '-') : '',
|
||||
template,
|
||||
selfApi = {
|
||||
compile: bind(self, self.compile),
|
||||
comment:function(text) {return jqLite(document.createComment(text));},
|
||||
element:function(type) {return jqLite(document.createElement(type));},
|
||||
text:function(text) {return jqLite(document.createTextNode(text));},
|
||||
descend: function(value){ if(isDefined(value)) descend = value; return descend;},
|
||||
directives: function(value){ if(isDefined(value)) directives = value; return directives;},
|
||||
scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;}
|
||||
@@ -199,6 +265,7 @@ Compiler.prototype = {
|
||||
// for some reason IE throws error under some weird circumstances. so just assume nothing
|
||||
priority = priority || 0;
|
||||
}
|
||||
element.addClass(elementNamespace);
|
||||
if (isString(priority)) {
|
||||
priority = PRIORITY[uppercase(priority)] || parseInt(priority, 10);
|
||||
}
|
||||
@@ -213,7 +280,7 @@ Compiler.prototype = {
|
||||
});
|
||||
if (!widget) {
|
||||
if (widget = self.widgets(elementName)) {
|
||||
if (elementName.indexOf(':') > 0)
|
||||
if (elementNamespace)
|
||||
element.addClass('ng-widget');
|
||||
widget = bind(selfApi, widget, element);
|
||||
}
|
||||
|
||||
@@ -5,41 +5,45 @@
|
||||
*
|
||||
* @description
|
||||
* Creates an inject function that can be used for dependency injection.
|
||||
* (See {@link guide.di dependency injection})
|
||||
*
|
||||
* The inject function can be used for retrieving service instances or for calling any function
|
||||
* which has the $inject property so that the services can be automatically provided. Angular
|
||||
* creates an injection function automatically for the root scope and it is available as
|
||||
* {@link angular.scope.$service $service}.
|
||||
*
|
||||
* @param {Object=} [providerScope={}] provider's `this`
|
||||
* @param {Object.<string, function()>=} [providers=angular.service] Map of provider (factory)
|
||||
* function.
|
||||
* @param {Object.<string, function()>=} [cache={}] Place where instances are saved for reuse. Can
|
||||
* also be used to override services speciafied by `providers` (useful in tests).
|
||||
* @returns {function()} Injector function.
|
||||
* @returns
|
||||
* {function()} Injector function: `function(value, scope, args...)`:
|
||||
*
|
||||
* * `value` - `{string|array|function}`
|
||||
* * `scope(optional=rootScope)` - optional function "`this`" when `value` is type `function`.
|
||||
* * `args(optional)` - optional set of arguments to pass to function after injection arguments.
|
||||
* (also known as curry arguments or currying).
|
||||
*
|
||||
* #Return value of `function(value, scope, args...)`
|
||||
* The injector function return value depended on the type of `value` argument:
|
||||
*
|
||||
* * `string`: return an instance for the injection key.
|
||||
* * `array` of keys: returns an array of instances for those keys. (see `string` above.)
|
||||
* * `function`: look at `$inject` property of function to determine instances to inject
|
||||
* and then call the function with instances and `scope`. Any additional arguments
|
||||
* (`args`) are appended to the function arguments.
|
||||
* * `none`: initialize eager providers.
|
||||
*
|
||||
* @TODO These docs need a lot of work. Specifically the returned function should be described in
|
||||
* great detail + we need to provide some examples.
|
||||
*/
|
||||
function createInjector(providerScope, providers, cache) {
|
||||
providers = providers || angularService;
|
||||
cache = cache || {};
|
||||
providerScope = providerScope || {};
|
||||
/**
|
||||
* injection function
|
||||
* @param value: string, array, object or function.
|
||||
* @param scope: optional function "this"
|
||||
* @param args: optional arguments to pass to function after injection
|
||||
* parameters
|
||||
* @returns depends on value:
|
||||
* string: return an instance for the injection key.
|
||||
* array of keys: returns an array of instances.
|
||||
* function: look at $inject property of function to determine instances
|
||||
* and then call the function with instances and `scope`. Any
|
||||
* additional arguments (`args`) are appended to the function
|
||||
* arguments.
|
||||
* object: initialize eager providers and publish them the ones with publish here.
|
||||
* none: same as object but use providerScope as place to publish.
|
||||
*/
|
||||
return function inject(value, scope, args){
|
||||
var returnValue, provider;
|
||||
if (isString(value)) {
|
||||
if (!cache.hasOwnProperty(value)) {
|
||||
if (!(value in cache)) {
|
||||
provider = providers[value];
|
||||
if (!provider) throw "Unknown provider for '"+value+"'.";
|
||||
cache[value] = inject(provider, providerScope);
|
||||
@@ -51,7 +55,7 @@ function createInjector(providerScope, providers, cache) {
|
||||
returnValue.push(inject(name));
|
||||
});
|
||||
} else if (isFunction(value)) {
|
||||
returnValue = inject(value.$inject || []);
|
||||
returnValue = inject(injectionArgs(value));
|
||||
returnValue = value.apply(scope, concat(returnValue, arguments, 2));
|
||||
} else if (isObject(value)) {
|
||||
forEach(providers, function(provider, name){
|
||||
@@ -76,3 +80,37 @@ function injectService(services, fn) {
|
||||
function injectUpdateView(fn) {
|
||||
return injectService(['$updateView'], fn);
|
||||
}
|
||||
|
||||
function angularServiceInject(name, fn, inject, eager) {
|
||||
angularService(name, fn, {$inject:inject, $eager:eager});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @returns the $inject property of function. If not found the
|
||||
* the $inject is computed by looking at the toString of function and
|
||||
* extracting all arguments which start with $ or end with _ as the
|
||||
* injection names.
|
||||
*/
|
||||
var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/;
|
||||
var FN_ARG_SPLIT = /,/;
|
||||
var FN_ARG = /^\s*(((\$?).+?)(_?))\s*$/;
|
||||
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||
function injectionArgs(fn) {
|
||||
assertArgFn(fn);
|
||||
if (!fn.$inject) {
|
||||
var args = fn.$inject = [];
|
||||
var fnText = fn.toString().replace(STRIP_COMMENTS, '');
|
||||
var argDecl = fnText.match(FN_ARGS);
|
||||
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
|
||||
arg.replace(FN_ARG, function(all, name, injectName, $, _){
|
||||
assertArg(args, name, 'after non-injectable arg');
|
||||
if ($ || _)
|
||||
args.push(injectName);
|
||||
else
|
||||
args = null; // once we reach an argument which is not injectable then ignore
|
||||
});
|
||||
});
|
||||
}
|
||||
return fn.$inject;
|
||||
};
|
||||
|
||||
@@ -13,19 +13,20 @@ function Route(template, defaults) {
|
||||
|
||||
Route.prototype = {
|
||||
url: function(params) {
|
||||
var path = [];
|
||||
var self = this;
|
||||
var url = this.template;
|
||||
var self = this,
|
||||
url = this.template,
|
||||
encodedVal;
|
||||
|
||||
params = params || {};
|
||||
forEach(this.urlParams, function(_, urlParam){
|
||||
var value = params[urlParam] || self.defaults[urlParam] || "";
|
||||
url = url.replace(new RegExp(":" + urlParam + "(\\W)"), value + "$1");
|
||||
encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "")
|
||||
url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
|
||||
});
|
||||
url = url.replace(/\/?#$/, '');
|
||||
var query = [];
|
||||
forEachSorted(params, function(value, key){
|
||||
if (!self.urlParams[key]) {
|
||||
query.push(encodeURI(key) + '=' + encodeURI(value));
|
||||
query.push(encodeUriSegment(key) + '=' + encodeUriSegment(value));
|
||||
}
|
||||
});
|
||||
url = url.replace(/\/*$/, '');
|
||||
|
||||
@@ -119,13 +119,13 @@ function errorHandlerFor(element, error) {
|
||||
* - Scopes can be nested. A scope (prototypically) inherits properties from its parent scope.
|
||||
* - Scopes can be attached (bound) to the HTML DOM tree (the view).
|
||||
* - A scope {@link angular.scope.$become becomes} `this` for a controller.
|
||||
* - Scope's {@link angular.scope.$eval $eval} is used to update its view.
|
||||
* - A scope's {@link angular.scope.$eval $eval} is used to update its view.
|
||||
* - Scopes can {@link angular.scope.$watch watch} properties and fire events.
|
||||
*
|
||||
* # Basic Operations
|
||||
* Scopes can be created by calling {@link angular.scope() angular.scope()} or by compiling HTML.
|
||||
*
|
||||
* {@link angular.widget Widgets} and data bindings register listeners on the current scope to get
|
||||
* {@link angular.widget Widgets} and data bindings register listeners on the current scope to be
|
||||
* notified of changes to the scope state. When notified, these listeners push the updated state
|
||||
* through to the DOM.
|
||||
*
|
||||
@@ -172,7 +172,7 @@ function errorHandlerFor(element, error) {
|
||||
*
|
||||
* # When scopes are evaluated
|
||||
* Anyone can update a scope by calling its {@link angular.scope.$eval $eval()} method. By default
|
||||
* angular widgets listen to user change events (e.g. the user enters text into text field), copy
|
||||
* angular widgets listen to user change events (e.g. the user enters text into a text field), copy
|
||||
* the data from the widget to the scope (the MVC model), and then call the `$eval()` method on the
|
||||
* root scope to update dependents. This creates a spreadsheet-like behavior: the bound views update
|
||||
* immediately as the user types into the text field.
|
||||
@@ -183,7 +183,7 @@ function errorHandlerFor(element, error) {
|
||||
*
|
||||
* Because a change in the model that's triggered either by user input or by server response calls
|
||||
* `$eval()`, it is unnecessary to call `$eval()` from within your controller. The only time when
|
||||
* calling `$eval()` is needed, is when implementing a custom widget or service.
|
||||
* calling `$eval()` is needed is when implementing a custom widget or service.
|
||||
*
|
||||
* Because scopes are inherited, the child scope `$eval()` overrides the parent `$eval()` method.
|
||||
* So to update the whole page you need to call `$eval()` on the root scope as `$root.$eval()`.
|
||||
@@ -202,7 +202,7 @@ function errorHandlerFor(element, error) {
|
||||
* @returns {Object} Newly created scope.
|
||||
*
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
* This example demonstrates scope inheritance and property overriding.
|
||||
*
|
||||
* In this example, the root scope encompasses the whole HTML DOM tree. This scope has `salutation`,
|
||||
@@ -216,27 +216,29 @@ function errorHandlerFor(element, error) {
|
||||
* - The child scope inherits the salutation property from the root scope.
|
||||
* - The $index property does not leak from the child scope to the root scope.
|
||||
*
|
||||
* @example
|
||||
<ul ng:init="salutation='Hello'; name='Misko'; names=['World', 'Earth']">
|
||||
<li ng:repeat="name in names">
|
||||
{{$index}}: {{salutation}} {{name}}!
|
||||
</li>
|
||||
</ul>
|
||||
<pre>
|
||||
$index={{$index}}
|
||||
salutation={{salutation}}
|
||||
name={{name}}</pre>
|
||||
|
||||
@scenario
|
||||
it('should inherit the salutation property and override the name property', function() {
|
||||
expect(using('.doc-example-live').repeater('li').row(0)).
|
||||
toEqual(['0', 'Hello', 'World']);
|
||||
expect(using('.doc-example-live').repeater('li').row(1)).
|
||||
toEqual(['1', 'Hello', 'Earth']);
|
||||
expect(using('.doc-example-live').element('pre').text()).
|
||||
toBe('$index=\nsalutation=Hello\nname=Misko');
|
||||
});
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<ul ng:init="salutation='Hello'; name='Misko'; names=['World', 'Earth']">
|
||||
<li ng:repeat="name in names">
|
||||
{{$index}}: {{salutation}} {{name}}!
|
||||
</li>
|
||||
</ul>
|
||||
<pre>
|
||||
$index={{$index}}
|
||||
salutation={{salutation}}
|
||||
name={{name}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should inherit the salutation property and override the name property', function() {
|
||||
expect(using('.doc-example-live').repeater('li').row(0)).
|
||||
toEqual(['0', 'Hello', 'World']);
|
||||
expect(using('.doc-example-live').repeater('li').row(1)).
|
||||
toEqual(['1', 'Hello', 'Earth']);
|
||||
expect(using('.doc-example-live').element('pre').text()).
|
||||
toBe(' $index=\n salutation=Hello\n name=Misko');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
function createScope(parent, providers, instanceCache) {
|
||||
function Parent(){}
|
||||
@@ -391,7 +393,7 @@ function createScope(parent, providers, instanceCache) {
|
||||
* {@link angular.scope.$eval()} with expression parameter, but also wraps it in a try/catch
|
||||
* block.
|
||||
*
|
||||
* If exception is thrown then `exceptionHandler` is used to handle the exception.
|
||||
* If an exception is thrown then `exceptionHandler` is used to handle the exception.
|
||||
*
|
||||
* # Example
|
||||
<pre>
|
||||
@@ -413,7 +415,7 @@ function createScope(parent, providers, instanceCache) {
|
||||
</pre>
|
||||
*
|
||||
* @param {string|function()} expression Angular expression to evaluate.
|
||||
* @param {function()|DOMElement} exceptionHandler Function to be called or DOMElement to be
|
||||
* @param {(function()|DOMElement)=} exceptionHandler Function to be called or DOMElement to be
|
||||
* decorated.
|
||||
* @returns {*} The result of `expression` evaluation.
|
||||
*/
|
||||
@@ -446,7 +448,7 @@ function createScope(parent, providers, instanceCache) {
|
||||
*
|
||||
* @description
|
||||
* Registers `listener` as a callback to be executed every time the `watchExp` changes. Be aware
|
||||
* that callback gets, by default, called upon registration, this can be prevented via the
|
||||
* that the callback gets, by default, called upon registration, this can be prevented via the
|
||||
* `initRun` parameter.
|
||||
*
|
||||
* # Example
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
function isActuallyNaN(val) {
|
||||
return isNaN(val) && (typeof val === 'number');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function addScripts(){
|
||||
var prop, i;
|
||||
@@ -118,7 +118,25 @@
|
||||
'AngularPublic.js',
|
||||
|
||||
// Extension points
|
||||
'services.js',
|
||||
|
||||
'service/cookieStore.js',
|
||||
'service/cookies.js',
|
||||
'service/defer.js',
|
||||
'service/document.js',
|
||||
'service/exceptionHandler.js',
|
||||
'service/hover.js',
|
||||
'service/invalidWidgets.js',
|
||||
'service/location.js',
|
||||
'service/log.js',
|
||||
'service/resource.js',
|
||||
'service/route.js',
|
||||
'service/updateView.js',
|
||||
'service/window.js',
|
||||
'service/xhr.bulk.js',
|
||||
'service/xhr.cache.js',
|
||||
'service/xhr.error.js',
|
||||
'service/xhr.js',
|
||||
|
||||
'apis.js',
|
||||
'filters.js',
|
||||
'formatters.js',
|
||||
|
||||
@@ -91,6 +91,7 @@ function MockBrowser() {
|
||||
self.pollFns.push(
|
||||
function() {
|
||||
if (self.lastUrl != self.url) {
|
||||
self.lastUrl = self.url;
|
||||
listener();
|
||||
}
|
||||
}
|
||||
@@ -141,6 +142,10 @@ function MockBrowser() {
|
||||
self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT');
|
||||
self.xhr.expectJSON = angular.bind(self, self.xhr.expect, 'JSON');
|
||||
self.xhr.flush = function() {
|
||||
if (requests.length == 0) {
|
||||
throw new Error("No xhr requests to be flushed!");
|
||||
}
|
||||
|
||||
while(requests.length) {
|
||||
requests.pop()();
|
||||
}
|
||||
@@ -236,12 +241,14 @@ angular.service('$exceptionHandler', function(e) {
|
||||
*
|
||||
* See {@link angular.mock} for more info on angular mocks.
|
||||
*/
|
||||
angular.service('$log', function() {
|
||||
angular.service('$log', MockLogFactory);
|
||||
|
||||
function MockLogFactory() {
|
||||
var $log = {
|
||||
log: function(){ $log.logs.push(arguments) },
|
||||
warn: function(){ $log.logs.push(arguments) },
|
||||
info: function(){ $log.logs.push(arguments) },
|
||||
error: function(){ $log.logs.push(arguments) }
|
||||
log: function(){ $log.log.logs.push(arguments); },
|
||||
warn: function(){ $log.warn.logs.push(arguments); },
|
||||
info: function(){ $log.info.logs.push(arguments); },
|
||||
error: function(){ $log.error.logs.push(arguments); }
|
||||
};
|
||||
|
||||
$log.log.logs = [];
|
||||
@@ -250,7 +257,7 @@ angular.service('$log', function() {
|
||||
$log.error.logs = [];
|
||||
|
||||
return $log;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -264,15 +271,6 @@ angular.service('$log', function() {
|
||||
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
|
||||
*
|
||||
* @example
|
||||
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
|
||||
* newYearInBratislava.getTimezoneOffset() => -60;
|
||||
* newYearInBratislava.getFullYear() => 2010;
|
||||
* newYearInBratislava.getMonth() => 0;
|
||||
* newYearInBratislava.getDate() => 1;
|
||||
* newYearInBratislava.getHours() => 0;
|
||||
* newYearInBratislava.getMinutes() => 0;
|
||||
*
|
||||
*
|
||||
* !!!! WARNING !!!!!
|
||||
* This is not a complete Date object so only methods that were implemented can be called safely.
|
||||
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
|
||||
@@ -280,6 +278,17 @@ angular.service('$log', function() {
|
||||
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
|
||||
* incomplete we might be missing some non-standard methods. This can result in errors like:
|
||||
* "Date.prototype.foo called on incompatible Object".
|
||||
*
|
||||
* <pre>
|
||||
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
|
||||
* newYearInBratislava.getTimezoneOffset() => -60;
|
||||
* newYearInBratislava.getFullYear() => 2010;
|
||||
* newYearInBratislava.getMonth() => 0;
|
||||
* newYearInBratislava.getDate() => 1;
|
||||
* newYearInBratislava.getHours() => 0;
|
||||
* newYearInBratislava.getMinutes() => 0;
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
function TzDate(offset, timestamp) {
|
||||
if (angular.isString(timestamp)) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
jqLite(document).ready(function(){
|
||||
jqLiteWrap(document).ready(function(){
|
||||
angularInit(angularJsConfig(document));
|
||||
});
|
||||
|
||||
|
||||
@@ -79,22 +79,26 @@ var angularArray = {
|
||||
* @returns {number} The position of the element in `array`. The position is 0-based. `-1` is returned if the value can't be found.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="books = ['Moby Dick', 'Great Gatsby', 'Romeo and Juliet']"></div>
|
||||
<input name='bookName' value='Romeo and Juliet'> <br>
|
||||
Index of '{{bookName}}' in the list {{books}} is <em>{{books.$indexOf(bookName)}}</em>.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="books = ['Moby Dick', 'Great Gatsby', 'Romeo and Juliet']"></div>
|
||||
<input name='bookName' value='Romeo and Juliet'> <br>
|
||||
Index of '{{bookName}}' in the list {{books}} is <em>{{books.$indexOf(bookName)}}</em>.
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should correctly calculate the initial index', function() {
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('2');
|
||||
});
|
||||
|
||||
@scenario
|
||||
it('should correctly calculate the initial index', function() {
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('2');
|
||||
});
|
||||
it('should recalculate', function() {
|
||||
input('bookName').enter('foo');
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('-1');
|
||||
|
||||
it('should recalculate', function() {
|
||||
input('bookName').enter('foo');
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('-1');
|
||||
|
||||
input('bookName').enter('Moby Dick');
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('0');
|
||||
});
|
||||
input('bookName').enter('Moby Dick');
|
||||
expect(binding('books.$indexOf(bookName)')).toBe('0');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
'indexOf': indexOf,
|
||||
|
||||
@@ -117,42 +121,46 @@ var angularArray = {
|
||||
* @returns {number} Sum of items in the array.
|
||||
*
|
||||
* @example
|
||||
<table ng:init="invoice= {items:[{qty:10, description:'gadget', cost:9.95}]}">
|
||||
<tr><th>Qty</th><th>Description</th><th>Cost</th><th>Total</th><th></th></tr>
|
||||
<tr ng:repeat="item in invoice.items">
|
||||
<td><input name="item.qty" value="1" size="4" ng:required ng:validate="integer"></td>
|
||||
<td><input name="item.description"></td>
|
||||
<td><input name="item.cost" value="0.00" ng:required ng:validate="number" size="6"></td>
|
||||
<td>{{item.qty * item.cost | currency}}</td>
|
||||
<td>[<a href ng:click="invoice.items.$remove(item)">X</a>]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href ng:click="invoice.items.$add()">add item</a></td>
|
||||
<td></td>
|
||||
<td>Total:</td>
|
||||
<td>{{invoice.items.$sum('qty*cost') | currency}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<table ng:init="invoice= {items:[{qty:10, description:'gadget', cost:9.95}]}">
|
||||
<tr><th>Qty</th><th>Description</th><th>Cost</th><th>Total</th><th></th></tr>
|
||||
<tr ng:repeat="item in invoice.items">
|
||||
<td><input name="item.qty" value="1" size="4" ng:required ng:validate="integer"></td>
|
||||
<td><input name="item.description"></td>
|
||||
<td><input name="item.cost" value="0.00" ng:required ng:validate="number" size="6"></td>
|
||||
<td>{{item.qty * item.cost | currency}}</td>
|
||||
<td>[<a href ng:click="invoice.items.$remove(item)">X</a>]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href ng:click="invoice.items.$add()">add item</a></td>
|
||||
<td></td>
|
||||
<td>Total:</td>
|
||||
<td>{{invoice.items.$sum('qty*cost') | currency}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
//TODO: these specs are lame because I had to work around issues #164 and #167
|
||||
it('should initialize and calculate the totals', function() {
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').count()).toBe(3);
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(1)).
|
||||
toEqual(['$99.50']);
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50');
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50');
|
||||
});
|
||||
|
||||
@scenario
|
||||
//TODO: these specs are lame because I had to work around issues #164 and #167
|
||||
it('should initialize and calculate the totals', function() {
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').count()).toBe(3);
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(1)).
|
||||
toEqual(['$99.50']);
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50');
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$99.50');
|
||||
});
|
||||
it('should add an entry and recalculate', function() {
|
||||
element('.doc-example-live a:contains("add item")').click();
|
||||
using('.doc-example-live tr:nth-child(3)').input('item.qty').enter('20');
|
||||
using('.doc-example-live tr:nth-child(3)').input('item.cost').enter('100');
|
||||
|
||||
it('should add an entry and recalculate', function() {
|
||||
element('.doc-example a:contains("add item")').click();
|
||||
using('.doc-example-live tr:nth-child(3)').input('item.qty').enter('20');
|
||||
using('.doc-example-live tr:nth-child(3)').input('item.cost').enter('100');
|
||||
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(2)).
|
||||
toEqual(['$2,000.00']);
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$2,099.50');
|
||||
});
|
||||
expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(2)).
|
||||
toEqual(['$2,000.00']);
|
||||
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$2,099.50');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
'sum':function(array, expression) {
|
||||
var fn = angular['Function']['compile'](expression);
|
||||
@@ -185,33 +193,37 @@ var angularArray = {
|
||||
* @returns {*} The removed element.
|
||||
*
|
||||
* @example
|
||||
<ul ng:init="tasks=['Learn Angular', 'Read Documentation',
|
||||
'Check out demos', 'Build cool applications']">
|
||||
<li ng:repeat="task in tasks">
|
||||
{{task}} [<a href="" ng:click="tasks.$remove(task)">X</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
tasks = {{tasks}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<ul ng:init="tasks=['Learn Angular', 'Read Documentation',
|
||||
'Check out demos', 'Build cool applications']">
|
||||
<li ng:repeat="task in tasks">
|
||||
{{task}} [<a href="" ng:click="tasks.$remove(task)">X</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<hr/>
|
||||
tasks = {{tasks}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should initialize the task list with for tasks', function() {
|
||||
expect(repeater('.doc-example-live ul li', 'task in tasks').count()).toBe(4);
|
||||
expect(repeater('.doc-example-live ul li', 'task in tasks').column('task')).
|
||||
toEqual(['Learn Angular', 'Read Documentation', 'Check out demos',
|
||||
'Build cool applications']);
|
||||
});
|
||||
|
||||
@scenario
|
||||
it('should initialize the task list with for tasks', function() {
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(4);
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').column('task')).
|
||||
toEqual(['Learn Angular', 'Read Documentation', 'Check out demos',
|
||||
'Build cool applications']);
|
||||
});
|
||||
it('should initialize the task list with for tasks', function() {
|
||||
element('.doc-example-live ul li a:contains("X"):first').click();
|
||||
expect(repeater('.doc-example-live ul li', 'task in tasks').count()).toBe(3);
|
||||
|
||||
it('should initialize the task list with for tasks', function() {
|
||||
element('.doc-example ul li a:contains("X"):first').click();
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(3);
|
||||
element('.doc-example-live ul li a:contains("X"):last').click();
|
||||
expect(repeater('.doc-example-live ul li', 'task in tasks').count()).toBe(2);
|
||||
|
||||
element('.doc-example ul li a:contains("X"):last').click();
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(2);
|
||||
|
||||
expect(repeater('.doc-example ul li', 'task in tasks').column('task')).
|
||||
toEqual(['Read Documentation', 'Check out demos']);
|
||||
});
|
||||
expect(repeater('.doc-example-live ul li', 'task in tasks').column('task')).
|
||||
toEqual(['Read Documentation', 'Check out demos']);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
'remove':function(array, value) {
|
||||
var index = indexOf(array, value);
|
||||
@@ -254,48 +266,52 @@ var angularArray = {
|
||||
* the predicate returned true for.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="friends = [{name:'John', phone:'555-1276'},
|
||||
{name:'Mary', phone:'800-BIG-MARY'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="friends = [{name:'John', phone:'555-1276'},
|
||||
{name:'Mary', phone:'800-BIG-MARY'},
|
||||
{name:'Mike', phone:'555-4321'},
|
||||
{name:'Adam', phone:'555-5678'},
|
||||
{name:'Julie', phone:'555-8765'}]"></div>
|
||||
|
||||
Search: <input name="searchText"/>
|
||||
<table id="searchTextResults">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(searchText)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
<hr>
|
||||
Any: <input name="search.$"/> <br>
|
||||
Name only <input name="search.name"/><br>
|
||||
Phone only <input name="search.phone"/><br>
|
||||
<table id="searchObjResults">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(search)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
Search: <input name="searchText"/>
|
||||
<table id="searchTextResults">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(searchText)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
<hr>
|
||||
Any: <input name="search.$"/> <br>
|
||||
Name only <input name="search.name"/><br>
|
||||
Phone only <input name="search.phone"/><br>
|
||||
<table id="searchObjResults">
|
||||
<tr><th>Name</th><th>Phone</th><tr>
|
||||
<tr ng:repeat="friend in friends.$filter(search)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should search across all fields when filtering with a string', function() {
|
||||
input('searchText').enter('m');
|
||||
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['Mary', 'Mike', 'Adam']);
|
||||
|
||||
@scenario
|
||||
it('should search across all fields when filtering with a string', function() {
|
||||
input('searchText').enter('m');
|
||||
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['Mary', 'Mike', 'Adam']);
|
||||
input('searchText').enter('76');
|
||||
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['John', 'Julie']);
|
||||
});
|
||||
|
||||
input('searchText').enter('76');
|
||||
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['John', 'Julie']);
|
||||
});
|
||||
|
||||
it('should search in specific fields when filtering with a predicate object', function() {
|
||||
input('search.$').enter('i');
|
||||
expect(repeater('#searchObjResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['Mary', 'Mike', 'Julie']);
|
||||
});
|
||||
it('should search in specific fields when filtering with a predicate object', function() {
|
||||
input('search.$').enter('i');
|
||||
expect(repeater('#searchObjResults tr', 'friend in friends').column('name')).
|
||||
toEqual(['Mary', 'Mike', 'Julie']);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
'filter':function(array, expression) {
|
||||
var predicates = [];
|
||||
@@ -398,53 +414,55 @@ var angularArray = {
|
||||
*
|
||||
* @TODO simplify the example.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
* This example shows how an initially empty array can be filled with objects created from user
|
||||
* input via the `$add` method.
|
||||
*
|
||||
* @example
|
||||
[<a href="" ng:click="people.$add()">add empty</a>]
|
||||
[<a href="" ng:click="people.$add({name:'John', sex:'male'})">add 'John'</a>]
|
||||
[<a href="" ng:click="people.$add({name:'Mary', sex:'female'})">add 'Mary'</a>]
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
[<a href="" ng:click="people.$add()">add empty</a>]
|
||||
[<a href="" ng:click="people.$add({name:'John', sex:'male'})">add 'John'</a>]
|
||||
[<a href="" ng:click="people.$add({name:'Mary', sex:'female'})">add 'Mary'</a>]
|
||||
|
||||
<ul ng:init="people=[]">
|
||||
<li ng:repeat="person in people">
|
||||
<input name="person.name">
|
||||
<select name="person.sex">
|
||||
<option value="">--chose one--</option>
|
||||
<option>male</option>
|
||||
<option>female</option>
|
||||
</select>
|
||||
[<a href="" ng:click="people.$remove(person)">X</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<pre>people = {{people}}</pre>
|
||||
<ul ng:init="people=[]">
|
||||
<li ng:repeat="person in people">
|
||||
<input name="person.name">
|
||||
<select name="person.sex">
|
||||
<option value="">--chose one--</option>
|
||||
<option>male</option>
|
||||
<option>female</option>
|
||||
</select>
|
||||
[<a href="" ng:click="people.$remove(person)">X</a>]
|
||||
</li>
|
||||
</ul>
|
||||
<pre>people = {{people}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
beforeEach(function() {
|
||||
expect(binding('people')).toBe('people = []');
|
||||
});
|
||||
|
||||
@scenario
|
||||
beforeEach(function() {
|
||||
expect(binding('people')).toBe('people = []');
|
||||
});
|
||||
it('should create an empty record when "add empty" is clicked', function() {
|
||||
element('.doc-example-live a:contains("add empty")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"",\n "sex":null}]');
|
||||
});
|
||||
|
||||
it('should create an empty record when "add empty" is clicked', function() {
|
||||
element('.doc-example a:contains("add empty")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"",\n "sex":null}]');
|
||||
});
|
||||
it('should create a "John" record when "add \'John\'" is clicked', function() {
|
||||
element('.doc-example-live a:contains("add \'John\'")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"John",\n "sex":"male"}]');
|
||||
});
|
||||
|
||||
it('should create a "John" record when "add \'John\'" is clicked', function() {
|
||||
element('.doc-example a:contains("add \'John\'")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"John",\n "sex":"male"}]');
|
||||
});
|
||||
it('should create a "Mary" record when "add \'Mary\'" is clicked', function() {
|
||||
element('.doc-example-live a:contains("add \'Mary\'")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"Mary",\n "sex":"female"}]');
|
||||
});
|
||||
|
||||
it('should create a "Mary" record when "add \'Mary\'" is clicked', function() {
|
||||
element('.doc-example a:contains("add \'Mary\'")').click();
|
||||
expect(binding('people')).toBe('people = [{\n "name":"Mary",\n "sex":"female"}]');
|
||||
});
|
||||
|
||||
it('should delete a record when "X" is clicked', function() {
|
||||
element('.doc-example a:contains("add empty")').click();
|
||||
element('.doc-example li a:contains("X"):first').click();
|
||||
expect(binding('people')).toBe('people = []');
|
||||
});
|
||||
it('should delete a record when "X" is clicked', function() {
|
||||
element('.doc-example-live a:contains("add empty")').click();
|
||||
element('.doc-example-live li a:contains("X"):first').click();
|
||||
expect(binding('people')).toBe('people = []');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
'add':function(array, value) {
|
||||
array.push(isUndefined(value)? {} : value);
|
||||
@@ -471,29 +489,33 @@ var angularArray = {
|
||||
* @returns {number} Number of elements in the array (for which the condition evaluates to true).
|
||||
*
|
||||
* @example
|
||||
<pre ng:init="items = [{name:'knife', points:1},
|
||||
{name:'fork', points:3},
|
||||
{name:'spoon', points:1}]"></pre>
|
||||
<ul>
|
||||
<li ng:repeat="item in items">
|
||||
{{item.name}}: points=
|
||||
<input type="text" name="item.points"/> <!-- id="item{{$index}} -->
|
||||
</li>
|
||||
</ul>
|
||||
<p>Number of items which have one point: <em>{{ items.$count('points==1') }}</em></p>
|
||||
<p>Number of items which have more than one point: <em>{{items.$count('points>1')}}</em></p>
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<pre ng:init="items = [{name:'knife', points:1},
|
||||
{name:'fork', points:3},
|
||||
{name:'spoon', points:1}]"></pre>
|
||||
<ul>
|
||||
<li ng:repeat="item in items">
|
||||
{{item.name}}: points=
|
||||
<input type="text" name="item.points"/> <!-- id="item{{$index}} -->
|
||||
</li>
|
||||
</ul>
|
||||
<p>Number of items which have one point: <em>{{ items.$count('points==1') }}</em></p>
|
||||
<p>Number of items which have more than one point: <em>{{items.$count('points>1')}}</em></p>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should calculate counts', function() {
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(2);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(1);
|
||||
});
|
||||
|
||||
@scenario
|
||||
it('should calculate counts', function() {
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(2);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should recalculate when updated', function() {
|
||||
using('.doc-example li:first-child').input('item.points').enter('23');
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(1);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(2);
|
||||
});
|
||||
it('should recalculate when updated', function() {
|
||||
using('.doc-example-live li:first-child').input('item.points').enter('23');
|
||||
expect(binding('items.$count(\'points==1\')')).toEqual(1);
|
||||
expect(binding('items.$count(\'points>1\')')).toEqual(2);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
'count':function(array, condition) {
|
||||
if (!condition) return array.length;
|
||||
@@ -535,52 +557,56 @@ var angularArray = {
|
||||
* @returns {Array} Sorted copy of the source array.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="friends = [{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}]"></div>
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="friends = [{name:'John', phone:'555-1212', age:10},
|
||||
{name:'Mary', phone:'555-9876', age:19},
|
||||
{name:'Mike', phone:'555-4321', age:21},
|
||||
{name:'Adam', phone:'555-5678', age:35},
|
||||
{name:'Julie', phone:'555-8765', age:29}]"></div>
|
||||
|
||||
<pre>Sorting predicate = {{predicate}}</pre>
|
||||
<hr/>
|
||||
<table ng:init="predicate='-age'">
|
||||
<tr>
|
||||
<th><a href="" ng:click="predicate = 'name'">Name</a>
|
||||
(<a href ng:click="predicate = '-name'">^</a>)</th>
|
||||
<th><a href="" ng:click="predicate = 'phone'">Phone</a>
|
||||
(<a href ng:click="predicate = '-phone'">^</a>)</th>
|
||||
<th><a href="" ng:click="predicate = 'age'">Age</a>
|
||||
(<a href ng:click="predicate = '-age'">^</a>)</th>
|
||||
<tr>
|
||||
<tr ng:repeat="friend in friends.$orderBy(predicate)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<td>{{friend.age}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
<pre>Sorting predicate = {{predicate}}</pre>
|
||||
<hr/>
|
||||
<table ng:init="predicate='-age'">
|
||||
<tr>
|
||||
<th><a href="" ng:click="predicate = 'name'">Name</a>
|
||||
(<a href ng:click="predicate = '-name'">^</a>)</th>
|
||||
<th><a href="" ng:click="predicate = 'phone'">Phone</a>
|
||||
(<a href ng:click="predicate = '-phone'">^</a>)</th>
|
||||
<th><a href="" ng:click="predicate = 'age'">Age</a>
|
||||
(<a href ng:click="predicate = '-age'">^</a>)</th>
|
||||
<tr>
|
||||
<tr ng:repeat="friend in friends.$orderBy(predicate)">
|
||||
<td>{{friend.name}}</td>
|
||||
<td>{{friend.phone}}</td>
|
||||
<td>{{friend.age}}</td>
|
||||
<tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should be reverse ordered by aged', function() {
|
||||
expect(binding('predicate')).toBe('Sorting predicate = -age');
|
||||
expect(repeater('.doc-example-live table', 'friend in friends').column('friend.age')).
|
||||
toEqual(['35', '29', '21', '19', '10']);
|
||||
expect(repeater('.doc-example-live table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
|
||||
});
|
||||
|
||||
@scenario
|
||||
it('should be reverse ordered by aged', function() {
|
||||
expect(binding('predicate')).toBe('Sorting predicate = -age');
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.age')).
|
||||
toEqual(['35', '29', '21', '19', '10']);
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
|
||||
});
|
||||
it('should reorder the table when user selects different predicate', function() {
|
||||
element('.doc-example-live a:contains("Name")').click();
|
||||
expect(repeater('.doc-example-live table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
|
||||
expect(repeater('.doc-example-live table', 'friend in friends').column('friend.age')).
|
||||
toEqual(['35', '10', '29', '19', '21']);
|
||||
|
||||
it('should reorder the table when user selects different predicate', function() {
|
||||
element('.doc-example a:contains("Name")').click();
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.age')).
|
||||
toEqual(['35', '10', '29', '19', '21']);
|
||||
|
||||
element('.doc-example a:contains("Phone")+a:contains("^")').click();
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.phone')).
|
||||
toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
|
||||
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
|
||||
});
|
||||
element('.doc-example-live a:contains("Phone")+a:contains("^")').click();
|
||||
expect(repeater('.doc-example-live table', 'friend in friends').column('friend.phone')).
|
||||
toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
|
||||
expect(repeater('.doc-example-live table', 'friend in friends').column('friend.name')).
|
||||
toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
//TODO: WTH is descend param for and how/when it should be used, how is it affected by +/- in
|
||||
// predicate? the code below is impossible to read and specs are not very good.
|
||||
@@ -611,8 +637,9 @@ var angularArray = {
|
||||
return 0;
|
||||
}
|
||||
function reverse(comp, descending) {
|
||||
return toBoolean(descending) ?
|
||||
function(a,b){return comp(b,a);} : comp;
|
||||
return toBoolean(descending)
|
||||
? function(a,b){return comp(b,a);}
|
||||
: comp;
|
||||
}
|
||||
function compare(v1, v2){
|
||||
var t1 = typeof v1;
|
||||
@@ -648,21 +675,25 @@ var angularArray = {
|
||||
* @returns {Array} A new sub-array of length `limit`.
|
||||
*
|
||||
* @example
|
||||
<div ng:init="numbers = [1,2,3,4,5,6,7,8,9]">
|
||||
Limit [1,2,3,4,5,6,7,8,9] to: <input name="limit" value="3"/>
|
||||
<p>Output: {{ numbers.$limitTo(limit) | json }}</p>
|
||||
</div>
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="numbers = [1,2,3,4,5,6,7,8,9]">
|
||||
Limit [1,2,3,4,5,6,7,8,9] to: <input name="limit" value="3"/>
|
||||
<p>Output: {{ numbers.$limitTo(limit) | json }}</p>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should limit the numer array to first three items', function() {
|
||||
expect(element('.doc-example-live input[name=limit]').val()).toBe('3');
|
||||
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3]');
|
||||
});
|
||||
|
||||
* @scenario
|
||||
it('should limit the numer array to first three items', function() {
|
||||
expect(element('.doc-example input[name=limit]').val()).toBe('3');
|
||||
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3]');
|
||||
});
|
||||
|
||||
it('should update the output when -3 is entered', function() {
|
||||
input('limit').enter(-3);
|
||||
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]');
|
||||
});
|
||||
it('should update the output when -3 is entered', function() {
|
||||
input('limit').enter(-3);
|
||||
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
limitTo: function(array, limit) {
|
||||
limit = parseInt(limit, 10);
|
||||
|
||||
@@ -4,22 +4,26 @@
|
||||
* @name angular.directive.ng:init
|
||||
*
|
||||
* @description
|
||||
* `ng:init` attribute allows the for initialization tasks to be executed
|
||||
* `ng:init` attribute allows the for initialization tasks to be executed
|
||||
* before the template enters execution mode during bootstrap.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="greeting='Hello'; person='World'">
|
||||
{{greeting}} {{person}}!
|
||||
</div>
|
||||
*
|
||||
* @scenario
|
||||
it('should check greeting', function(){
|
||||
expect(binding('greeting')).toBe('Hello');
|
||||
expect(binding('person')).toBe('World');
|
||||
});
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check greeting', function(){
|
||||
expect(binding('greeting')).toBe('Hello');
|
||||
expect(binding('person')).toBe('World');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:init", function(expression){
|
||||
return function(element){
|
||||
@@ -33,66 +37,76 @@ angularDirective("ng:init", function(expression){
|
||||
* @name angular.directive.ng:controller
|
||||
*
|
||||
* @description
|
||||
* To support the Model-View-Controller design pattern, it is possible
|
||||
* to assign behavior to a scope through `ng:controller`. The scope is
|
||||
* the MVC model. The HTML (with data bindings) is the MVC view.
|
||||
* To support the Model-View-Controller design pattern, it is possible
|
||||
* to assign behavior to a scope through `ng:controller`. The scope is
|
||||
* the MVC model. The HTML (with data bindings) is the MVC view.
|
||||
* The `ng:controller` directive specifies the MVC controller class
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval.
|
||||
*
|
||||
* @example
|
||||
<script type="text/javascript">
|
||||
function SettingsController() {
|
||||
this.name = "John Smith";
|
||||
this.contacts = [
|
||||
{type:'phone', value:'408 555 1212'},
|
||||
{type:'email', value:'john.smith@example.org'} ];
|
||||
}
|
||||
SettingsController.prototype = {
|
||||
greet: function(){
|
||||
alert(this.name);
|
||||
},
|
||||
addContact: function(){
|
||||
this.contacts.push({type:'email', value:'yourname@example.org'});
|
||||
},
|
||||
removeContact: function(contactToRemove) {
|
||||
angular.Array.remove(this.contacts, contactToRemove);
|
||||
},
|
||||
clearContact: function(contact) {
|
||||
contact.type = 'phone';
|
||||
contact.value = '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="SettingsController">
|
||||
Name: <input type="text" name="name"/>
|
||||
[ <a href="" ng:click="greet()">greet</a> ]<br/>
|
||||
Contact:
|
||||
<ul>
|
||||
<li ng:repeat="contact in contacts">
|
||||
<select name="contact.type">
|
||||
<option>phone</option>
|
||||
<option>email</option>
|
||||
</select>
|
||||
<input type="text" name="contact.value"/>
|
||||
[ <a href="" ng:click="clearContact(contact)">clear</a>
|
||||
| <a href="" ng:click="removeContact(contact)">X</a> ]
|
||||
</li>
|
||||
<li>[ <a href="" ng:click="addContact()">add</a> ]</li>
|
||||
</ul>
|
||||
</div>
|
||||
*
|
||||
* @scenario
|
||||
it('should check controller', function(){
|
||||
expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212');
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org');
|
||||
element('.doc-example-live li:first a:contains("clear")').click();
|
||||
expect(element('.doc-example-live li:first input').val()).toBe('');
|
||||
element('.doc-example-live li:last a:contains("add")').click();
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="2"] input').val()).toBe('yourname@example.org');
|
||||
});
|
||||
* Here is a simple form for editing the user contact information. Adding, removing clearing and
|
||||
* greeting are methods which are declared on the controller (see source tab). These methods can
|
||||
* easily be called from the angular markup. Notice that the scope becomes the controller's class
|
||||
* this. This allows for easy access to the view data from the controller. Also notice that any
|
||||
* changes to the data are automatically reflected in the view without the need to update it
|
||||
* manually.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script type="text/javascript">
|
||||
function SettingsController() {
|
||||
this.name = "John Smith";
|
||||
this.contacts = [
|
||||
{type:'phone', value:'408 555 1212'},
|
||||
{type:'email', value:'john.smith@example.org'} ];
|
||||
}
|
||||
SettingsController.prototype = {
|
||||
greet: function(){
|
||||
alert(this.name);
|
||||
},
|
||||
addContact: function(){
|
||||
this.contacts.push({type:'email', value:'yourname@example.org'});
|
||||
},
|
||||
removeContact: function(contactToRemove) {
|
||||
angular.Array.remove(this.contacts, contactToRemove);
|
||||
},
|
||||
clearContact: function(contact) {
|
||||
contact.type = 'phone';
|
||||
contact.value = '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<div ng:controller="SettingsController">
|
||||
Name: <input type="text" name="name"/>
|
||||
[ <a href="" ng:click="greet()">greet</a> ]<br/>
|
||||
Contact:
|
||||
<ul>
|
||||
<li ng:repeat="contact in contacts">
|
||||
<select name="contact.type">
|
||||
<option>phone</option>
|
||||
<option>email</option>
|
||||
</select>
|
||||
<input type="text" name="contact.value"/>
|
||||
[ <a href="" ng:click="clearContact(contact)">clear</a>
|
||||
| <a href="" ng:click="removeContact(contact)">X</a> ]
|
||||
</li>
|
||||
<li>[ <a href="" ng:click="addContact()">add</a> ]</li>
|
||||
</ul>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check controller', function(){
|
||||
expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212');
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org');
|
||||
element('.doc-example-live li:first a:contains("clear")').click();
|
||||
expect(element('.doc-example-live li:first input').val()).toBe('');
|
||||
element('.doc-example-live li:last a:contains("add")').click();
|
||||
expect(element('.doc-example-live li[ng\\:repeat-index="2"] input').val()).toBe('yourname@example.org');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:controller", function(expression){
|
||||
this.scope(true);
|
||||
@@ -112,36 +126,38 @@ angularDirective("ng:controller", function(expression){
|
||||
* @name angular.directive.ng:eval
|
||||
*
|
||||
* @description
|
||||
* The `ng:eval` allows you to execute a binding which has side effects
|
||||
* The `ng:eval` allows you to execute a binding which has side effects
|
||||
* without displaying the result to the user.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning
|
||||
* a value to `obj.multiplied` and displaying the result to the user. Sometimes,
|
||||
* however, it is desirable to execute a side effect without showing the value to
|
||||
* the user. In such a case `ng:eval` allows you to execute code without updating
|
||||
* the display.
|
||||
*
|
||||
* @example
|
||||
* <input name="obj.a" value="6" >
|
||||
* * <input name="obj.b" value="2">
|
||||
* = {{obj.multiplied = obj.a * obj.b}} <br>
|
||||
* <span ng:eval="obj.divide = obj.a / obj.b"></span>
|
||||
* <span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"></span>
|
||||
* <tt>obj.divide = {{obj.divide}}</tt><br/>
|
||||
* <tt>obj.updateCount = {{obj.updateCount}}</tt>
|
||||
*
|
||||
* @scenario
|
||||
it('should check eval', function(){
|
||||
expect(binding('obj.divide')).toBe('3');
|
||||
expect(binding('obj.updateCount')).toBe('2');
|
||||
input('obj.a').enter('12');
|
||||
expect(binding('obj.divide')).toBe('6');
|
||||
expect(binding('obj.updateCount')).toBe('3');
|
||||
});
|
||||
* Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning
|
||||
* a value to `obj.multiplied` and displaying the result to the user. Sometimes,
|
||||
* however, it is desirable to execute a side effect without showing the value to
|
||||
* the user. In such a case `ng:eval` allows you to execute code without updating
|
||||
* the display.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<input name="obj.a" value="6" >
|
||||
* <input name="obj.b" value="2">
|
||||
= {{obj.multiplied = obj.a * obj.b}} <br>
|
||||
<span ng:eval="obj.divide = obj.a / obj.b"></span>
|
||||
<span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"></span>
|
||||
<tt>obj.divide = {{obj.divide}}</tt><br/>
|
||||
<tt>obj.updateCount = {{obj.updateCount}}</tt>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check eval', function(){
|
||||
expect(binding('obj.divide')).toBe('3');
|
||||
expect(binding('obj.updateCount')).toBe('2');
|
||||
input('obj.a').enter('12');
|
||||
expect(binding('obj.divide')).toBe('6');
|
||||
expect(binding('obj.updateCount')).toBe('3');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:eval", function(expression){
|
||||
return function(element){
|
||||
@@ -155,27 +171,30 @@ angularDirective("ng:eval", function(expression){
|
||||
* @name angular.directive.ng:bind
|
||||
*
|
||||
* @description
|
||||
* The `ng:bind` attribute asks <angular/> to replace the text content of this
|
||||
* HTML element with the value of the given expression and kept it up to
|
||||
* date when the expression's value changes. Usually you just write
|
||||
* {{expression}} and let <angular/> compile it into
|
||||
* The `ng:bind` attribute asks <angular/> to replace the text content of this
|
||||
* HTML element with the value of the given expression and kept it up to
|
||||
* date when the expression's value changes. Usually you just write
|
||||
* {{expression}} and let <angular/> compile it into
|
||||
* `<span ng:bind="expression"></span>` at bootstrap time.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Try it here: enter text in text box and watch the greeting change.
|
||||
* @element ANY
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval.
|
||||
*
|
||||
* @example
|
||||
* Enter name: <input type="text" name="name" value="Whirled">. <br>
|
||||
* Hello <span ng:bind="name" />!
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:bind', function(){
|
||||
expect(using('.doc-example-live').binding('name')).toBe('Whirled');
|
||||
using('.doc-example-live').input('name').enter('world');
|
||||
expect(using('.doc-example-live').binding('name')).toBe('world');
|
||||
});
|
||||
* Try it here: enter text in text box and watch the greeting change.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Enter name: <input type="text" name="name" value="Whirled">. <br>
|
||||
Hello <span ng:bind="name" />!
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:bind', function(){
|
||||
expect(using('.doc-example-live').binding('name')).toBe('Whirled');
|
||||
using('.doc-example-live').input('name').enter('world');
|
||||
expect(using('.doc-example-live').binding('name')).toBe('world');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:bind", function(expression, element){
|
||||
element.addClass('ng-binding');
|
||||
@@ -224,15 +243,17 @@ function compileBindTemplate(template){
|
||||
var bindings = [];
|
||||
forEach(parseBindings(template), function(text){
|
||||
var exp = binding(text);
|
||||
bindings.push(exp ? function(element){
|
||||
var error, value = this.$tryEval(exp, function(e){
|
||||
error = toJson(e);
|
||||
});
|
||||
elementError(element, NG_EXCEPTION, error);
|
||||
return error ? error : value;
|
||||
} : function() {
|
||||
return text;
|
||||
});
|
||||
bindings.push(exp
|
||||
? function(element){
|
||||
var error, value = this.$tryEval(exp, function(e){
|
||||
error = toJson(e);
|
||||
});
|
||||
elementError(element, NG_EXCEPTION, error);
|
||||
return error ? error : value;
|
||||
}
|
||||
: function() {
|
||||
return text;
|
||||
});
|
||||
});
|
||||
bindTemplateCache[template] = fn = function(element, prettyPrintJson){
|
||||
var parts = [], self = this,
|
||||
@@ -259,32 +280,35 @@ function compileBindTemplate(template){
|
||||
* @name angular.directive.ng:bind-template
|
||||
*
|
||||
* @description
|
||||
* The `ng:bind-template` attribute specifies that the element
|
||||
* text should be replaced with the template in ng:bind-template.
|
||||
* Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}`
|
||||
* expressions. (This is required since some HTML elements
|
||||
* The `ng:bind-template` attribute specifies that the element
|
||||
* text should be replaced with the template in ng:bind-template.
|
||||
* Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}`
|
||||
* expressions. (This is required since some HTML elements
|
||||
* can not have SPAN elements such as TITLE, or OPTION to name a few.
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} template of form
|
||||
* <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Try it here: enter text in text box and watch the greeting change.
|
||||
* @example
|
||||
Salutation: <input type="text" name="salutation" value="Hello"><br/>
|
||||
Name: <input type="text" name="name" value="World"><br/>
|
||||
<pre ng:bind-template="{{salutation}} {{name}}!"></pre>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:bind', function(){
|
||||
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
|
||||
toBe('Hello World!');
|
||||
using('.doc-example-live').input('salutation').enter('Greetings');
|
||||
using('.doc-example-live').input('name').enter('user');
|
||||
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
|
||||
toBe('Greetings user!');
|
||||
});
|
||||
* Try it here: enter text in text box and watch the greeting change.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Salutation: <input type="text" name="salutation" value="Hello"><br/>
|
||||
Name: <input type="text" name="name" value="World"><br/>
|
||||
<pre ng:bind-template="{{salutation}} {{name}}!"></pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:bind', function(){
|
||||
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
|
||||
toBe('Hello World!');
|
||||
using('.doc-example-live').input('salutation').enter('Greetings');
|
||||
using('.doc-example-live').input('name').enter('user');
|
||||
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
|
||||
toBe('Greetings user!');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:bind-template", function(expression, element){
|
||||
element.addClass('ng-binding');
|
||||
@@ -313,49 +337,52 @@ var REMOVE_ATTRIBUTES = {
|
||||
* @name angular.directive.ng:bind-attr
|
||||
*
|
||||
* @description
|
||||
* The `ng:bind-attr` attribute specifies that the element attributes
|
||||
* which should be replaced by the expression in it. Unlike `ng:bind`
|
||||
* the `ng:bind-attr` contains a JSON key value pairs representing
|
||||
* which attributes need to be changed. You don’t usually write the
|
||||
* `ng:bind-attr` in the HTML since embedding
|
||||
* <tt ng:non-bindable>{{expression}}</tt> into the
|
||||
* attribute directly is the preferred way. The attributes get
|
||||
* translated into `<span ng:bind-attr="{attr:expression}"/>` at
|
||||
* bootstrap time.
|
||||
*
|
||||
* The `ng:bind-attr` attribute specifies that {@link guide.data-binding databindings} should be
|
||||
* created between element attributes and given expressions. Unlike `ng:bind` the `ng:bind-attr`
|
||||
* contains a JSON key value pairs representing which attributes need to be mapped to which
|
||||
* {@link guide.expression expressions}.
|
||||
*
|
||||
* You don’t usually write the `ng:bind-attr` in the HTML since embedding
|
||||
* <tt ng:non-bindable>{{expression}}</tt> into the attribute directly as the attribute value is
|
||||
* preferred. The attributes get translated into `<span ng:bind-attr="{attr:expression}"/>` at
|
||||
* compile time.
|
||||
*
|
||||
* This HTML snippet is preferred way of working with `ng:bind-attr`
|
||||
* <pre>
|
||||
* <a href="http://www.google.com/search?q={{query}}">Google</a>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* The above gets translated to bellow during bootstrap time.
|
||||
* <pre>
|
||||
* <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>Google</a>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {string} attribute_json a JSON key-value pairs representing
|
||||
* the attributes to replace. Each key matches the attribute
|
||||
* which needs to be replaced. Each value is a text template of
|
||||
* the attribute with embedded
|
||||
* <tt ng:non-bindable>{{expression}}</tt>s. Any number of
|
||||
* @param {string} attribute_json a JSON key-value pairs representing
|
||||
* the attributes to replace. Each key matches the attribute
|
||||
* which needs to be replaced. Each value is a text template of
|
||||
* the attribute with embedded
|
||||
* <tt ng:non-bindable>{{expression}}</tt>s. Any number of
|
||||
* key-value pairs can be specified.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Try it here: enter text in text box and click Google.
|
||||
* @example
|
||||
Google for:
|
||||
<input type="text" name="query" value="AngularJS"/>
|
||||
<a href="http://www.google.com/search?q={{query}}">Google</a>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:bind-attr', function(){
|
||||
expect(using('.doc-example-live').element('a').attr('href')).
|
||||
toBe('http://www.google.com/search?q=AngularJS');
|
||||
using('.doc-example-live').input('query').enter('google');
|
||||
expect(using('.doc-example-live').element('a').attr('href')).
|
||||
toBe('http://www.google.com/search?q=google');
|
||||
});
|
||||
* Try it here: enter text in text box and click Google.
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Google for:
|
||||
<input type="text" name="query" value="AngularJS"/>
|
||||
<a href="http://www.google.com/search?q={{query}}">Google</a>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:bind-attr', function(){
|
||||
expect(using('.doc-example-live').element('a').attr('href')).
|
||||
toBe('http://www.google.com/search?q=AngularJS');
|
||||
using('.doc-example-live').input('query').enter('google');
|
||||
expect(using('.doc-example-live').element('a').attr('href')).
|
||||
toBe('http://www.google.com/search?q=google');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:bind-attr", function(expression){
|
||||
return function(element){
|
||||
@@ -396,23 +423,28 @@ angularDirective("ng:bind-attr", function(expression){
|
||||
* @name angular.directive.ng:click
|
||||
*
|
||||
* @description
|
||||
* The ng:click allows you to specify custom behavior when
|
||||
* The ng:click allows you to specify custom behavior when
|
||||
* element is clicked.
|
||||
*
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval upon click.
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval upon click.
|
||||
*
|
||||
* @example
|
||||
<button ng:click="count = count + 1" ng:init="count=0">
|
||||
Increment
|
||||
</button>
|
||||
count: {{count}}
|
||||
* @scenario
|
||||
it('should check ng:click', function(){
|
||||
expect(binding('count')).toBe('0');
|
||||
element('.doc-example-live :button').click();
|
||||
expect(binding('count')).toBe('1');
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<button ng:click="count = count + 1" ng:init="count=0">
|
||||
Increment
|
||||
</button>
|
||||
count: {{count}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:click', function(){
|
||||
expect(binding('count')).toBe('0');
|
||||
element('.doc-example-live :button').click();
|
||||
expect(binding('count')).toBe('1');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
/*
|
||||
* A directive that allows creation of custom onclick handlers that are defined as angular
|
||||
@@ -440,35 +472,37 @@ angularDirective("ng:click", function(expression, element){
|
||||
* @name angular.directive.ng:submit
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @element form
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
* <form ng:submit="list.push(text);text='';" ng:init="list=[]">
|
||||
* Enter text and hit enter:
|
||||
* <input type="text" name="text" value="hello"/>
|
||||
* </form>
|
||||
* <pre>list={{list}}</pre>
|
||||
* @scenario
|
||||
it('should check ng:submit', function(){
|
||||
expect(binding('list')).toBe('list=[]');
|
||||
element('.doc-example-live form input').click();
|
||||
this.addFutureAction('submit from', function($window, $document, done) {
|
||||
$window.angular.element(
|
||||
$document.elements('.doc-example-live form')).
|
||||
trigger('submit');
|
||||
done();
|
||||
});
|
||||
expect(binding('list')).toBe('list=["hello"]');
|
||||
});
|
||||
*/
|
||||
/**
|
||||
* Enables binding angular expressions to onsubmit events.
|
||||
*
|
||||
* Additionally it prevents the default action (which for form means sending the request to the
|
||||
* server and reloading the current page).
|
||||
*
|
||||
* @element form
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<form ng:submit="list.push(text);text='';" ng:init="list=[]">
|
||||
Enter text and hit enter:
|
||||
<input type="text" name="text" value="hello"/>
|
||||
</form>
|
||||
<pre>list={{list}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:submit', function(){
|
||||
expect(binding('list')).toBe('list=[]');
|
||||
element('.doc-example-live form input').click();
|
||||
this.addFutureAction('submit from', function($window, $document, done) {
|
||||
$window.angular.element(
|
||||
$document.elements('.doc-example-live form')).
|
||||
trigger('submit');
|
||||
done();
|
||||
});
|
||||
expect(binding('list')).toBe('list=["hello"]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:submit", function(expression, element) {
|
||||
return injectUpdateView(function($updateView, element) {
|
||||
@@ -482,47 +516,6 @@ angularDirective("ng:submit", function(expression, element) {
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc directive
|
||||
* @name angular.directive.ng:watch
|
||||
*
|
||||
* @description
|
||||
* The `ng:watch` allows you watch a variable and then execute
|
||||
* an evaluation on variable change.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* Notice that the counter is incremented
|
||||
* every time you change the text.
|
||||
* @example
|
||||
<div ng:init="counter=0" ng:watch="name: counter = counter+1">
|
||||
<input type="text" name="name" value="hello"><br/>
|
||||
Change counter: {{counter}} Name: {{name}}
|
||||
</div>
|
||||
* @scenario
|
||||
it('should check ng:watch', function(){
|
||||
expect(using('.doc-example-live').binding('counter')).toBe('2');
|
||||
using('.doc-example-live').input('name').enter('abc');
|
||||
expect(using('.doc-example-live').binding('counter')).toBe('3');
|
||||
});
|
||||
*/
|
||||
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
|
||||
angularDirective("ng:watch", function(expression, element){
|
||||
return function(element){
|
||||
var self = this;
|
||||
parser(expression).watch()({
|
||||
addListener:function(watch, exp){
|
||||
self.$watch(watch, function(){
|
||||
return exp(self);
|
||||
}, element);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
function ngClass(selector) {
|
||||
return function(expression, element){
|
||||
var existing = element[0].className + ' ';
|
||||
@@ -544,34 +537,37 @@ function ngClass(selector) {
|
||||
* @name angular.directive.ng:class
|
||||
*
|
||||
* @description
|
||||
* The `ng:class` allows you to set CSS class on HTML element
|
||||
* The `ng:class` allows you to set CSS class on HTML element
|
||||
* conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @element ANY
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval.
|
||||
*
|
||||
* @example
|
||||
<input type="button" value="set" ng:click="myVar='ng-input-indicator-wait'">
|
||||
<input type="button" value="clear" ng:click="myVar=''">
|
||||
<br>
|
||||
<span ng:class="myVar">Sample Text </span>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:class', function(){
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<input type="button" value="set" ng:click="myVar='ng-input-indicator-wait'">
|
||||
<input type="button" value="clear" ng:click="myVar=''">
|
||||
<br>
|
||||
<span ng:class="myVar">Sample Text </span>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:class', function(){
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
|
||||
using('.doc-example-live').element(':button:first').click();
|
||||
using('.doc-example-live').element(':button:first').click();
|
||||
|
||||
expect(element('.doc-example-live span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
expect(element('.doc-example-live span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
|
||||
using('.doc-example-live').element(':button:last').click();
|
||||
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
using('.doc-example-live').element(':button:last').click();
|
||||
|
||||
expect(element('.doc-example-live span').attr('className')).not().
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:class", ngClass(function(){return true;}));
|
||||
|
||||
@@ -581,33 +577,35 @@ angularDirective("ng:class", ngClass(function(){return true;}));
|
||||
* @name angular.directive.ng:class-odd
|
||||
*
|
||||
* @description
|
||||
* The `ng:class-odd` and `ng:class-even` works exactly as
|
||||
* `ng:class`, except it works in conjunction with `ng:repeat`
|
||||
* The `ng:class-odd` and `ng:class-even` works exactly as
|
||||
* `ng:class`, except it works in conjunction with `ng:repeat`
|
||||
* and takes affect only on odd (even) rows.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval. Must be inside
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval. Must be inside
|
||||
* `ng:repeat`.
|
||||
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
|
||||
<li ng:repeat="name in names">
|
||||
<span ng:class-odd="'ng-format-negative'"
|
||||
ng:class-even="'ng-input-indicator-wait'">
|
||||
{{name}}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
|
||||
<li ng:repeat="name in names">
|
||||
<span ng:class-odd="'ng-format-negative'"
|
||||
ng:class-even="'ng-input-indicator-wait'">
|
||||
{{name}}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
|
||||
|
||||
@@ -617,33 +615,35 @@ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
|
||||
* @name angular.directive.ng:class-even
|
||||
*
|
||||
* @description
|
||||
* The `ng:class-odd` and `ng:class-even` works exactly as
|
||||
* `ng:class`, except it works in conjunction with `ng:repeat`
|
||||
* The `ng:class-odd` and `ng:class-even` works exactly as
|
||||
* `ng:class`, except it works in conjunction with `ng:repeat`
|
||||
* and takes affect only on odd (even) rows.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression to eval. Must be inside
|
||||
* @param {expression} expression {@link guide.expression Expression} to eval. Must be inside
|
||||
* `ng:repeat`.
|
||||
|
||||
*
|
||||
* @exampleDescription
|
||||
* @example
|
||||
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
|
||||
<li ng:repeat="name in names">
|
||||
<span ng:class-odd="'ng-format-negative'"
|
||||
ng:class-even="'ng-input-indicator-wait'">
|
||||
{{name}}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
|
||||
<li ng:repeat="name in names">
|
||||
<span ng:class-odd="'ng-format-negative'"
|
||||
ng:class-even="'ng-input-indicator-wait'">
|
||||
{{name}}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:class-odd and ng:class-even', function(){
|
||||
expect(element('.doc-example-live li:first span').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
expect(element('.doc-example-live li:last span').attr('className')).
|
||||
toMatch(/ng-input-indicator-wait/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
|
||||
|
||||
@@ -655,27 +655,30 @@ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
|
||||
* @description
|
||||
* The `ng:show` and `ng:hide` allows you to show or hide a portion
|
||||
* of the HTML conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression if truthy then the element is
|
||||
* shown or hidden respectively.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @element ANY
|
||||
* @param {expression} expression If the {@link guide.expression expression} is truthy then the element
|
||||
* is shown or hidden respectively.
|
||||
*
|
||||
* @example
|
||||
Click me: <input type="checkbox" name="checked"><br/>
|
||||
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
|
||||
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:show / ng:hide', function(){
|
||||
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
|
||||
|
||||
input('checked').check();
|
||||
|
||||
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Click me: <input type="checkbox" name="checked"><br/>
|
||||
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
|
||||
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:show / ng:hide', function(){
|
||||
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
|
||||
|
||||
input('checked').check();
|
||||
|
||||
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:show", function(expression, element){
|
||||
return function(element){
|
||||
@@ -693,27 +696,30 @@ angularDirective("ng:show", function(expression, element){
|
||||
* @description
|
||||
* The `ng:show` and `ng:hide` allows you to show or hide a portion
|
||||
* of the HTML conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression if truthy then the element is
|
||||
* shown or hidden respectively.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @element ANY
|
||||
* @param {expression} expression If the {@link guide.expression expression} truthy then the element
|
||||
* is shown or hidden respectively.
|
||||
*
|
||||
* @example
|
||||
Click me: <input type="checkbox" name="checked"><br/>
|
||||
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
|
||||
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:show / ng:hide', function(){
|
||||
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
|
||||
|
||||
input('checked').check();
|
||||
|
||||
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Click me: <input type="checkbox" name="checked"><br/>
|
||||
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
|
||||
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:show / ng:hide', function(){
|
||||
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
|
||||
|
||||
input('checked').check();
|
||||
|
||||
expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
|
||||
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:hide", function(expression, element){
|
||||
return function(element){
|
||||
@@ -730,28 +736,30 @@ angularDirective("ng:hide", function(expression, element){
|
||||
*
|
||||
* @description
|
||||
* The ng:style allows you to set CSS style on an HTML element conditionally.
|
||||
*
|
||||
* @element ANY
|
||||
* @param {expression} expression which evals to an object whes key's are
|
||||
* CSS style names and values are coresponding values for those
|
||||
* CSS keys.
|
||||
*
|
||||
* @exampleDescription
|
||||
* @element ANY
|
||||
* @param {expression} expression {@link guide.expression Expression} which evals to an object whose
|
||||
* keys are CSS style names and values are corresponding values for those CSS keys.
|
||||
*
|
||||
* @example
|
||||
<input type="button" value="set" ng:click="myStyle={color:'red'}">
|
||||
<input type="button" value="clear" ng:click="myStyle={}">
|
||||
<br/>
|
||||
<span ng:style="myStyle">Sample Text</span>
|
||||
<pre>myStyle={{myStyle}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
it('should check ng:style', function(){
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
element('.doc-example-live :button[value=set]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('red');
|
||||
element('.doc-example-live :button[value=clear]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<input type="button" value="set" ng:click="myStyle={color:'red'}">
|
||||
<input type="button" value="clear" ng:click="myStyle={}">
|
||||
<br/>
|
||||
<span ng:style="myStyle">Sample Text</span>
|
||||
<pre>myStyle={{myStyle}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should check ng:style', function(){
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
element('.doc-example-live :button[value=set]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('red');
|
||||
element('.doc-example-live :button[value=clear]').click();
|
||||
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularDirective("ng:style", function(expression, element){
|
||||
return function(element){
|
||||
|
||||
@@ -14,19 +14,23 @@
|
||||
* When the value is negative, this css class is applied to the binding making it by default red.
|
||||
*
|
||||
* @example
|
||||
<input type="text" name="amount" value="1234.56"/> <br/>
|
||||
{{amount | currency}}
|
||||
*
|
||||
* @scenario
|
||||
it('should init with 1234.56', function(){
|
||||
expect(binding('amount | currency')).toBe('$1,234.56');
|
||||
});
|
||||
it('should update', function(){
|
||||
input('amount').enter('-1234');
|
||||
expect(binding('amount | currency')).toBe('$-1,234.00');
|
||||
expect(element('.doc-example-live .ng-binding').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<input type="text" name="amount" value="1234.56"/> <br/>
|
||||
{{amount | currency}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should init with 1234.56', function(){
|
||||
expect(binding('amount | currency')).toBe('$1,234.56');
|
||||
});
|
||||
it('should update', function(){
|
||||
input('amount').enter('-1234');
|
||||
expect(binding('amount | currency')).toBe('$-1,234.00');
|
||||
expect(element('.doc-example-live .ng-binding').attr('className')).
|
||||
toMatch(/ng-format-negative/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFilter.currency = function(amount){
|
||||
this.$element.toggleClass('ng-format-negative', amount < 0);
|
||||
@@ -49,24 +53,28 @@ angularFilter.currency = function(amount){
|
||||
* @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
|
||||
*
|
||||
* @example
|
||||
Enter number: <input name='val' value='1234.56789' /><br/>
|
||||
Default formatting: {{val | number}}<br/>
|
||||
No fractions: {{val | number:0}}<br/>
|
||||
Negative number: {{-val | number:4}}
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Enter number: <input name='val' value='1234.56789' /><br/>
|
||||
Default formatting: {{val | number}}<br/>
|
||||
No fractions: {{val | number:0}}<br/>
|
||||
Negative number: {{-val | number:4}}
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should format numbers', function(){
|
||||
expect(binding('val | number')).toBe('1,234.57');
|
||||
expect(binding('val | number:0')).toBe('1,235');
|
||||
expect(binding('-val | number:4')).toBe('-1,234.5679');
|
||||
});
|
||||
|
||||
* @scenario
|
||||
it('should format numbers', function(){
|
||||
expect(binding('val | number')).toBe('1,234.57');
|
||||
expect(binding('val | number:0')).toBe('1,235');
|
||||
expect(binding('-val | number:4')).toBe('-1,234.5679');
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
input('val').enter('3374.333');
|
||||
expect(binding('val | number')).toBe('3,374.33');
|
||||
expect(binding('val | number:0')).toBe('3,374');
|
||||
expect(binding('-val | number:4')).toBe('-3,374.3330');
|
||||
});
|
||||
it('should update', function(){
|
||||
input('val').enter('3374.333');
|
||||
expect(binding('val | number')).toBe('3,374.33');
|
||||
expect(binding('val | number:0')).toBe('3,374');
|
||||
expect(binding('-val | number:4')).toBe('-3,374.3330');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFilter.number = function(number, fractionSize){
|
||||
if (isNaN(number) || !isFinite(number)) {
|
||||
@@ -183,19 +191,22 @@ var NUMBER_STRING = /^\d+$/;
|
||||
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
|
||||
*
|
||||
* @example
|
||||
<span ng:non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
|
||||
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/>
|
||||
<span ng:non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
|
||||
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br/>
|
||||
*
|
||||
* @scenario
|
||||
it('should format date', function(){
|
||||
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
|
||||
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
|
||||
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(am|pm)/);
|
||||
});
|
||||
*
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<span ng:non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
|
||||
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/>
|
||||
<span ng:non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
|
||||
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br/>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should format date', function(){
|
||||
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
|
||||
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
|
||||
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
|
||||
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(am|pm)/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFilter.date = function(date, format) {
|
||||
if (isString(date)) {
|
||||
@@ -255,19 +266,23 @@ angularFilter.date = function(date, format) {
|
||||
* @css ng-monospace Always applied to the encapsulating element.
|
||||
*
|
||||
* @example:
|
||||
<input type="text" name="objTxt" value="{a:1, b:[]}"
|
||||
ng:eval="obj = $eval(objTxt)"/>
|
||||
<pre>{{ obj | json }}</pre>
|
||||
*
|
||||
* @scenario
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}');
|
||||
});
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<input type="text" name="objTxt" value="{a:1, b:[]}"
|
||||
ng:eval="obj = $eval(objTxt)"/>
|
||||
<pre>{{ obj | json }}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}');
|
||||
});
|
||||
|
||||
it('should update', function() {
|
||||
input('objTxt').enter('[1, 2, 3]');
|
||||
expect(binding('obj | json')).toBe('[1,2,3]');
|
||||
});
|
||||
it('should update', function() {
|
||||
input('objTxt').enter('[1, 2, 3]');
|
||||
expect(binding('obj | json')).toBe('[1,2,3]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*
|
||||
*/
|
||||
angularFilter.json = function(object) {
|
||||
@@ -324,63 +339,67 @@ angularFilter.uppercase = uppercase;
|
||||
* @returns {string} Sanitized or raw html.
|
||||
*
|
||||
* @example
|
||||
Snippet: <textarea name="snippet" cols="60" rows="3">
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Snippet: <textarea name="snippet" cols="60" rows="3">
|
||||
<p style="color:blue">an html
|
||||
<em onmouseover="this.textContent='PWN3D!'">click here</em>
|
||||
snippet</p></textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="html-filter">
|
||||
<td>html filter</td>
|
||||
<td>
|
||||
<pre><div ng:bind="snippet | html"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng:bind="snippet | html"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet"></div></td>
|
||||
</tr>
|
||||
<tr id="html-unsafe-filter">
|
||||
<td>unsafe html filter</td>
|
||||
<td><pre><div ng:bind="snippet | html:'unsafe'"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet | html:'unsafe'"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
*
|
||||
* @scenario
|
||||
it('should sanitize the html snippet ', function(){
|
||||
expect(using('#html-filter').binding('snippet | html')).
|
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
||||
});
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="html-filter">
|
||||
<td>html filter</td>
|
||||
<td>
|
||||
<pre><div ng:bind="snippet | html"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng:bind="snippet | html"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet"></div></td>
|
||||
</tr>
|
||||
<tr id="html-unsafe-filter">
|
||||
<td>unsafe html filter</td>
|
||||
<td><pre><div ng:bind="snippet | html:'unsafe'"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet | html:'unsafe'"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should sanitize the html snippet ', function(){
|
||||
expect(using('#html-filter').binding('snippet | html')).
|
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
||||
});
|
||||
|
||||
it ('should escape snippet without any filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
it('should escape snippet without any filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it ('should inline raw snippet if filtered as unsafe', function() {
|
||||
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
it('should inline raw snippet if filtered as unsafe', function() {
|
||||
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).
|
||||
toBe("<p style=\"color:blue\">an html\n" +
|
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
||||
"snippet</p>");
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
input('snippet').enter('new <b>text</b>');
|
||||
expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>");
|
||||
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new <b>text</b>');
|
||||
});
|
||||
it('should update', function(){
|
||||
input('snippet').enter('new <b>text</b>');
|
||||
expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe("new <b>text</b>");
|
||||
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).toBe('new <b>text</b>');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFilter.html = function(html, option){
|
||||
return new HTML(html, option);
|
||||
@@ -401,59 +420,63 @@ angularFilter.html = function(html, option){
|
||||
* @returns {string} Html-linkified text.
|
||||
*
|
||||
* @example
|
||||
Snippet: <textarea name="snippet" cols="60" rows="3">
|
||||
Pretty text with some links:
|
||||
http://angularjs.org/,
|
||||
mailto:us@somewhere.org,
|
||||
another@somewhere.org,
|
||||
and one more: ftp://127.0.0.1/.</textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="linky-filter">
|
||||
<td>linky filter</td>
|
||||
<td>
|
||||
<pre><div ng:bind="snippet | linky"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng:bind="snippet | linky"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Snippet: <textarea name="snippet" cols="60" rows="3">
|
||||
Pretty text with some links:
|
||||
http://angularjs.org/,
|
||||
mailto:us@somewhere.org,
|
||||
another@somewhere.org,
|
||||
and one more: ftp://127.0.0.1/.</textarea>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Filter</td>
|
||||
<td>Source</td>
|
||||
<td>Rendered</td>
|
||||
</tr>
|
||||
<tr id="linky-filter">
|
||||
<td>linky filter</td>
|
||||
<td>
|
||||
<pre><div ng:bind="snippet | linky"><br/></div></pre>
|
||||
</td>
|
||||
<td>
|
||||
<div ng:bind="snippet | linky"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="escaped-html">
|
||||
<td>no filter</td>
|
||||
<td><pre><div ng:bind="snippet"><br/></div></pre></td>
|
||||
<td><div ng:bind="snippet"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should linkify the snippet with urls', function(){
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('Pretty text with some links:\n' +
|
||||
'<a href="http://angularjs.org/">http://angularjs.org/</a>,\n' +
|
||||
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,\n' +
|
||||
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,\n' +
|
||||
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
|
||||
});
|
||||
|
||||
@scenario
|
||||
it('should linkify the snippet with urls', function(){
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('Pretty text with some links:\n' +
|
||||
'<a href="http://angularjs.org/">http://angularjs.org/</a>,\n' +
|
||||
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,\n' +
|
||||
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,\n' +
|
||||
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
|
||||
});
|
||||
it ('should not linkify snippet without the linky filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("Pretty text with some links:\n" +
|
||||
"http://angularjs.org/,\n" +
|
||||
"mailto:us@somewhere.org,\n" +
|
||||
"another@somewhere.org,\n" +
|
||||
"and one more: ftp://127.0.0.1/.");
|
||||
});
|
||||
|
||||
it ('should not linkify snippet without the linky filter', function() {
|
||||
expect(using('#escaped-html').binding('snippet')).
|
||||
toBe("Pretty text with some links:\n" +
|
||||
"http://angularjs.org/,\n" +
|
||||
"mailto:us@somewhere.org,\n" +
|
||||
"another@somewhere.org,\n" +
|
||||
"and one more: ftp://127.0.0.1/.");
|
||||
});
|
||||
|
||||
it('should update', function(){
|
||||
input('snippet').enter('new http://link.');
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('new <a href="http://link">http://link</a>.');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
|
||||
});
|
||||
it('should update', function(){
|
||||
input('snippet').enter('new http://link.');
|
||||
expect(using('#linky-filter').binding('snippet | linky')).
|
||||
toBe('new <a href="http://link">http://link</a>.');
|
||||
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
//TODO: externalize all regexps
|
||||
angularFilter.linky = function(text){
|
||||
|
||||
@@ -18,17 +18,21 @@ angularFormatter.noop = formatter(identity, identity);
|
||||
* @returns {?string} A JSON string representation of the model.
|
||||
*
|
||||
* @example
|
||||
* <div ng:init="data={name:'misko', project:'angular'}">
|
||||
* <input type="text" size='50' name="data" ng:format="json"/>
|
||||
* <pre>data={{data}}</pre>
|
||||
* </div>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format json', function(){
|
||||
* expect(binding('data')).toEqual('data={\n \"name\":\"misko\",\n \"project\":\"angular\"}');
|
||||
* input('data').enter('{}');
|
||||
* expect(binding('data')).toEqual('data={\n }');
|
||||
* });
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<div ng:init="data={name:'misko', project:'angular'}">
|
||||
<input type="text" size='50' name="data" ng:format="json"/>
|
||||
<pre>data={{data}}</pre>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should format json', function(){
|
||||
expect(binding('data')).toEqual('data={\n \"name\":\"misko\",\n \"project\":\"angular\"}');
|
||||
input('data').enter('{}');
|
||||
expect(binding('data')).toEqual('data={\n }');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFormatter.json = formatter(toJson, function(value){
|
||||
return fromJson(value || 'null');
|
||||
@@ -45,17 +49,21 @@ angularFormatter.json = formatter(toJson, function(value){
|
||||
* @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
|
||||
*
|
||||
* @example
|
||||
* Enter truthy text:
|
||||
* <input type="text" name="value" ng:format="boolean" value="no"/>
|
||||
* <input type="checkbox" name="value"/>
|
||||
* <pre>value={{value}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format boolean', function(){
|
||||
* expect(binding('value')).toEqual('value=false');
|
||||
* input('value').enter('truthy');
|
||||
* expect(binding('value')).toEqual('value=true');
|
||||
* });
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Enter truthy text:
|
||||
<input type="text" name="value" ng:format="boolean" value="no"/>
|
||||
<input type="checkbox" name="value"/>
|
||||
<pre>value={{value}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should format boolean', function(){
|
||||
expect(binding('value')).toEqual('value=false');
|
||||
input('value').enter('truthy');
|
||||
expect(binding('value')).toEqual('value=true');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFormatter['boolean'] = formatter(toString, toBoolean);
|
||||
|
||||
@@ -70,16 +78,20 @@ angularFormatter['boolean'] = formatter(toString, toBoolean);
|
||||
* @returns {number} Number from the parsed string.
|
||||
*
|
||||
* @example
|
||||
* Enter valid number:
|
||||
* <input type="text" name="value" ng:format="number" value="1234"/>
|
||||
* <pre>value={{value}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format numbers', function(){
|
||||
* expect(binding('value')).toEqual('value=1234');
|
||||
* input('value').enter('5678');
|
||||
* expect(binding('value')).toEqual('value=5678');
|
||||
* });
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Enter valid number:
|
||||
<input type="text" name="value" ng:format="number" value="1234"/>
|
||||
<pre>value={{value}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should format numbers', function(){
|
||||
expect(binding('value')).toEqual('value=1234');
|
||||
input('value').enter('5678');
|
||||
expect(binding('value')).toEqual('value=5678');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFormatter.number = formatter(toString, function(obj){
|
||||
if (obj == _null || NUMBER.exec(obj)) {
|
||||
@@ -100,20 +112,24 @@ angularFormatter.number = formatter(toString, function(obj){
|
||||
* @returns {Array} Array parsed from the entered string.
|
||||
*
|
||||
* @example
|
||||
* Enter a list of items:
|
||||
* <input type="text" name="value" ng:format="list" value=" chair ,, table"/>
|
||||
* <input type="text" name="value" ng:format="list"/>
|
||||
* <pre>value={{value}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format lists', function(){
|
||||
* expect(binding('value')).toEqual('value=["chair","table"]');
|
||||
* this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
* $document.elements('.doc-example :input:last').val(',,a,b,').trigger('change');
|
||||
* done();
|
||||
* });
|
||||
* expect(binding('value')).toEqual('value=["a","b"]');
|
||||
* });
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Enter a list of items:
|
||||
<input type="text" name="value" ng:format="list" value=" chair ,, table"/>
|
||||
<input type="text" name="value" ng:format="list"/>
|
||||
<pre>value={{value}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should format lists', function(){
|
||||
expect(binding('value')).toEqual('value=["chair","table"]');
|
||||
this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
$document.elements('.doc-example-live :input:last').val(',,a,b,').trigger('change');
|
||||
done();
|
||||
});
|
||||
expect(binding('value')).toEqual('value=["a","b"]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFormatter.list = formatter(
|
||||
function(obj) { return obj ? obj.join(", ") : obj; },
|
||||
@@ -138,20 +154,24 @@ angularFormatter.list = formatter(
|
||||
* @returns {String} Trim excess leading and trailing space.
|
||||
*
|
||||
* @example
|
||||
* Enter text with leading/trailing spaces:
|
||||
* <input type="text" name="value" ng:format="trim" value=" book "/>
|
||||
* <input type="text" name="value" ng:format="trim"/>
|
||||
* <pre>value={{value|json}}</pre>
|
||||
*
|
||||
* @scenario
|
||||
* it('should format trim', function(){
|
||||
* expect(binding('value')).toEqual('value="book"');
|
||||
* this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
* $document.elements('.doc-example :input:last').val(' text ').trigger('change');
|
||||
* done();
|
||||
* });
|
||||
* expect(binding('value')).toEqual('value="text"');
|
||||
* });
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
Enter text with leading/trailing spaces:
|
||||
<input type="text" name="value" ng:format="trim" value=" book "/>
|
||||
<input type="text" name="value" ng:format="trim"/>
|
||||
<pre>value={{value|json}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should format trim', function(){
|
||||
expect(binding('value')).toEqual('value="book"');
|
||||
this.addFutureAction('change to XYZ', function($window, $document, done){
|
||||
$document.elements('.doc-example-live :input:last').val(' text ').trigger('change');
|
||||
done();
|
||||
});
|
||||
expect(binding('value')).toEqual('value="text"');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFormatter.trim = formatter(
|
||||
function(obj) { return obj ? trim("" + obj) : ""; }
|
||||
@@ -176,33 +196,36 @@ angularFormatter.trim = formatter(
|
||||
* @returns {object} object which is located at the selected position.
|
||||
*
|
||||
* @example
|
||||
* <script>
|
||||
* function DemoCntl(){
|
||||
* this.users = [
|
||||
* {name:'guest', password:'guest'},
|
||||
* {name:'user', password:'123'},
|
||||
* {name:'admin', password:'abc'}
|
||||
* ];
|
||||
* }
|
||||
* </script>
|
||||
* <div ng:controller="DemoCntl">
|
||||
* User:
|
||||
* <select name="currentUser" ng:format="index:users">
|
||||
* <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
|
||||
* </select>
|
||||
* <select name="currentUser" ng:format="index:users">
|
||||
* <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
|
||||
* </select>
|
||||
* user={{currentUser.name}}<br/>
|
||||
* password={{currentUser.password}}<br/>
|
||||
* </div>
|
||||
*
|
||||
* @scenario
|
||||
* it('should retrieve object by index', function(){
|
||||
* expect(binding('currentUser.password')).toEqual('guest');
|
||||
* select('currentUser').option('2');
|
||||
* expect(binding('currentUser.password')).toEqual('abc');
|
||||
* });
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function DemoCntl(){
|
||||
this.users = [
|
||||
{name:'guest', password:'guest'},
|
||||
{name:'user', password:'123'},
|
||||
{name:'admin', password:'abc'}
|
||||
];
|
||||
}
|
||||
</script>
|
||||
<div ng:controller="DemoCntl">
|
||||
User:
|
||||
<select name="currentUser" ng:format="index:users">
|
||||
<option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
|
||||
</select>
|
||||
<select name="currentUser" ng:format="index:users">
|
||||
<option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
|
||||
</select>
|
||||
user={{currentUser.name}}<br/>
|
||||
password={{currentUser.password}}<br/>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should retrieve object by index', function(){
|
||||
expect(binding('currentUser.password')).toEqual('guest');
|
||||
select('currentUser').option('2');
|
||||
expect(binding('currentUser.password')).toEqual('abc');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularFormatter.index = formatter(
|
||||
function(object, array){
|
||||
|
||||
@@ -5,29 +5,15 @@
|
||||
var jqCache = {},
|
||||
jqName = 'ng-' + new Date().getTime(),
|
||||
jqId = 1,
|
||||
addEventListenerFn = (window.document.addEventListener ?
|
||||
function(element, type, fn) {element.addEventListener(type, fn, false);} :
|
||||
function(element, type, fn) {element.attachEvent('on' + type, fn);}),
|
||||
removeEventListenerFn = (window.document.removeEventListener ?
|
||||
function(element, type, fn) {element.removeEventListener(type, fn, false); } :
|
||||
function(element, type, fn) {element.detachEvent('on' + type, fn); });
|
||||
addEventListenerFn = (window.document.addEventListener
|
||||
? function(element, type, fn) {element.addEventListener(type, fn, false);}
|
||||
: function(element, type, fn) {element.attachEvent('on' + type, fn);}),
|
||||
removeEventListenerFn = (window.document.removeEventListener
|
||||
? function(element, type, fn) {element.removeEventListener(type, fn, false); }
|
||||
: function(element, type, fn) {element.detachEvent('on' + type, fn); });
|
||||
|
||||
function jqNextId() { return (jqId++); }
|
||||
|
||||
function jqClearData(element) {
|
||||
var cacheId = element[jqName],
|
||||
cache = jqCache[cacheId];
|
||||
if (cache) {
|
||||
forEach(cache.bind || {}, function(fn, type){
|
||||
removeEventListenerFn(element, type, fn);
|
||||
});
|
||||
delete jqCache[cacheId];
|
||||
if (msie)
|
||||
element[jqName] = ''; // ie does not allow deletion of attributes on elements.
|
||||
else
|
||||
delete element[jqName];
|
||||
}
|
||||
}
|
||||
|
||||
function getStyle(element) {
|
||||
var current = {}, style = element[0].style, value, name, i;
|
||||
@@ -46,203 +32,6 @@ function getStyle(element) {
|
||||
return current;
|
||||
}
|
||||
|
||||
function JQLite(element) {
|
||||
if (!isElement(element) && isDefined(element.length) && element.item && !isWindow(element)) {
|
||||
for(var i=0; i < element.length; i++) {
|
||||
this[i] = element[i];
|
||||
}
|
||||
this.length = element.length;
|
||||
} else {
|
||||
this[0] = element;
|
||||
this.length = 1;
|
||||
}
|
||||
}
|
||||
|
||||
JQLite.prototype = {
|
||||
data: function(key, value) {
|
||||
var element = this[0],
|
||||
cacheId = element[jqName],
|
||||
cache = jqCache[cacheId || -1];
|
||||
if (isDefined(value)) {
|
||||
if (!cache) {
|
||||
element[jqName] = cacheId = jqNextId();
|
||||
cache = jqCache[cacheId] = {};
|
||||
}
|
||||
cache[key] = value;
|
||||
} else {
|
||||
return cache ? cache[key] : _null;
|
||||
}
|
||||
},
|
||||
|
||||
removeData: function(){
|
||||
jqClearData(this[0]);
|
||||
},
|
||||
|
||||
dealoc: function(){
|
||||
(function dealoc(element){
|
||||
jqClearData(element);
|
||||
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
|
||||
dealoc(children[i]);
|
||||
}
|
||||
})(this[0]);
|
||||
},
|
||||
|
||||
ready: function(fn) {
|
||||
var fired = false;
|
||||
|
||||
function trigger() {
|
||||
if (fired) return;
|
||||
fired = true;
|
||||
fn();
|
||||
}
|
||||
|
||||
this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
|
||||
jqLite(window).bind('load', trigger); // fallback to window.onload for others
|
||||
},
|
||||
|
||||
bind: function(type, fn){
|
||||
var self = this,
|
||||
element = self[0],
|
||||
bind = self.data('bind'),
|
||||
eventHandler;
|
||||
if (!bind) this.data('bind', bind = {});
|
||||
forEach(type.split(' '), function(type){
|
||||
eventHandler = bind[type];
|
||||
if (!eventHandler) {
|
||||
bind[type] = eventHandler = function(event) {
|
||||
if (!event.preventDefault) {
|
||||
event.preventDefault = function(){
|
||||
event.returnValue = false; //ie
|
||||
};
|
||||
}
|
||||
if (!event.stopPropagation) {
|
||||
event.stopPropagation = function() {
|
||||
event.cancelBubble = true; //ie
|
||||
};
|
||||
}
|
||||
forEach(eventHandler.fns, function(fn){
|
||||
fn.call(self, event);
|
||||
});
|
||||
};
|
||||
eventHandler.fns = [];
|
||||
addEventListenerFn(element, type, eventHandler);
|
||||
}
|
||||
eventHandler.fns.push(fn);
|
||||
});
|
||||
},
|
||||
|
||||
replaceWith: function(replaceNode) {
|
||||
this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]);
|
||||
},
|
||||
|
||||
children: function() {
|
||||
return new JQLite(this[0].childNodes);
|
||||
},
|
||||
|
||||
append: function(node) {
|
||||
var self = this[0];
|
||||
node = jqLite(node);
|
||||
forEach(node, function(child){
|
||||
self.appendChild(child);
|
||||
});
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
this.dealoc();
|
||||
var parentNode = this[0].parentNode;
|
||||
if (parentNode) parentNode.removeChild(this[0]);
|
||||
},
|
||||
|
||||
removeAttr: function(name) {
|
||||
this[0].removeAttribute(name);
|
||||
},
|
||||
|
||||
after: function(element) {
|
||||
this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling);
|
||||
},
|
||||
|
||||
hasClass: function(selector) {
|
||||
var className = " " + selector + " ";
|
||||
if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
removeClass: function(selector) {
|
||||
this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", ""));
|
||||
},
|
||||
|
||||
toggleClass: function(selector, condition) {
|
||||
var self = this;
|
||||
(condition ? self.addClass : self.removeClass).call(self, selector);
|
||||
},
|
||||
|
||||
addClass: function( selector ) {
|
||||
if (!this.hasClass(selector)) {
|
||||
this[0].className = trim(this[0].className + ' ' + selector);
|
||||
}
|
||||
},
|
||||
|
||||
css: function(name, value) {
|
||||
var style = this[0].style;
|
||||
if (isString(name)) {
|
||||
if (isDefined(value)) {
|
||||
style[name] = value;
|
||||
} else {
|
||||
return style[name];
|
||||
}
|
||||
} else {
|
||||
extend(style, name);
|
||||
}
|
||||
},
|
||||
|
||||
attr: function(name, value){
|
||||
var e = this[0];
|
||||
if (isObject(name)) {
|
||||
forEach(name, function(value, name){
|
||||
e.setAttribute(name, value);
|
||||
});
|
||||
} else if (isDefined(value)) {
|
||||
e.setAttribute(name, value);
|
||||
} else {
|
||||
// the extra argument is to get the right thing for a.href in IE, see jQuery code
|
||||
return e.getAttribute(name, 2);
|
||||
}
|
||||
},
|
||||
|
||||
text: function(value) {
|
||||
if (isDefined(value)) {
|
||||
this[0].textContent = value;
|
||||
}
|
||||
return this[0].textContent;
|
||||
},
|
||||
|
||||
val: function(value) {
|
||||
if (isDefined(value)) {
|
||||
this[0].value = value;
|
||||
}
|
||||
return this[0].value;
|
||||
},
|
||||
|
||||
html: function(value) {
|
||||
if (isDefined(value)) {
|
||||
var i = 0, childNodes = this[0].childNodes;
|
||||
for ( ; i < childNodes.length; i++) {
|
||||
jqLite(childNodes[i]).dealoc();
|
||||
}
|
||||
this[0].innerHTML = value;
|
||||
}
|
||||
return this[0].innerHTML;
|
||||
},
|
||||
|
||||
parent: function() {
|
||||
return jqLite(this[0].parentNode);
|
||||
},
|
||||
|
||||
clone: function() { return jqLite(this[0].cloneNode(true)); }
|
||||
};
|
||||
|
||||
if (msie) {
|
||||
extend(JQLite.prototype, {
|
||||
text: function(value) {
|
||||
@@ -258,3 +47,355 @@ if (msie) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
function jqLiteWrap(element) {
|
||||
if (isString(element) && element.charAt(0) != '<') {
|
||||
throw new Error('selectors not implemented');
|
||||
}
|
||||
return new JQLite(element);
|
||||
}
|
||||
|
||||
function JQLite(element) {
|
||||
if (element instanceof JQLite) {
|
||||
return element;
|
||||
} else if (isString(element)) {
|
||||
var div = document.createElement('div');
|
||||
// Read about the NoScope elements here:
|
||||
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
|
||||
div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work!
|
||||
div.removeChild(div.firstChild); // remove the superfluous div
|
||||
JQLiteAddNodes(this, div.childNodes);
|
||||
this.remove(); // detach the elements form the temporary DOM div.
|
||||
} else {
|
||||
JQLiteAddNodes(this, element);
|
||||
}
|
||||
}
|
||||
|
||||
function JQLiteClone(element) {
|
||||
return element.cloneNode(true);
|
||||
}
|
||||
|
||||
function JQLiteDealoc(element){
|
||||
JQLiteRemoveData(element);
|
||||
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
|
||||
JQLiteDealoc(children[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function JQLiteRemoveData(element) {
|
||||
var cacheId = element[jqName],
|
||||
cache = jqCache[cacheId];
|
||||
if (cache) {
|
||||
forEach(cache.bind || {}, function(fn, type){
|
||||
removeEventListenerFn(element, type, fn);
|
||||
});
|
||||
delete jqCache[cacheId];
|
||||
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
|
||||
}
|
||||
}
|
||||
|
||||
function JQLiteData(element, key, value) {
|
||||
var cacheId = element[jqName],
|
||||
cache = jqCache[cacheId || -1];
|
||||
if (isDefined(value)) {
|
||||
if (!cache) {
|
||||
element[jqName] = cacheId = jqNextId();
|
||||
cache = jqCache[cacheId] = {};
|
||||
}
|
||||
cache[key] = value;
|
||||
} else {
|
||||
return cache ? cache[key] : _null;
|
||||
}
|
||||
}
|
||||
|
||||
function JQLiteHasClass(element, selector, _) {
|
||||
// the argument '_' is important, since it makes the function have 3 arguments, which
|
||||
// is neede for delegate function to realize the this is a getter.
|
||||
var className = " " + selector + " ";
|
||||
return ((" " + element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1);
|
||||
}
|
||||
|
||||
function JQLiteRemoveClass(element, selector) {
|
||||
element.className = trim(
|
||||
(" " + element.className + " ")
|
||||
.replace(/[\n\t]/g, " ")
|
||||
.replace(" " + selector + " ", "")
|
||||
);
|
||||
}
|
||||
|
||||
function JQLiteAddClass(element, selector ) {
|
||||
if (!JQLiteHasClass(element, selector)) {
|
||||
element.className = trim(element.className + ' ' + selector);
|
||||
}
|
||||
}
|
||||
|
||||
function JQLiteAddNodes(root, elements) {
|
||||
if (elements) {
|
||||
elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
|
||||
? elements
|
||||
: [ elements ];
|
||||
for(var i=0; i < elements.length; i++) {
|
||||
if (elements[i].nodeType != 11)
|
||||
root.push(elements[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Functions which are declared directly.
|
||||
//////////////////////////////////////////
|
||||
var JQLitePrototype = JQLite.prototype = extend([], {
|
||||
ready: function(fn) {
|
||||
var fired = false;
|
||||
|
||||
function trigger() {
|
||||
if (fired) return;
|
||||
fired = true;
|
||||
fn();
|
||||
}
|
||||
|
||||
this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
|
||||
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
|
||||
jqLiteWrap(window).bind('load', trigger); // fallback to window.onload for others
|
||||
},
|
||||
toString: function(){
|
||||
var value = [];
|
||||
forEach(this, function(e){ value.push('' + e);});
|
||||
return '[' + value.join(', ') + ']';
|
||||
}
|
||||
});
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Functions iterating getter/setters.
|
||||
// these functions return self on setter and
|
||||
// value on get.
|
||||
//////////////////////////////////////////
|
||||
forEach({
|
||||
data: JQLiteData,
|
||||
|
||||
scope: function(element) {
|
||||
var scope;
|
||||
while (element && !(scope = jqLite(element).data($$scope))) {
|
||||
element = element.parentNode;
|
||||
}
|
||||
return scope;
|
||||
},
|
||||
|
||||
removeAttr: function(element,name) {
|
||||
element.removeAttribute(name);
|
||||
},
|
||||
|
||||
hasClass: JQLiteHasClass,
|
||||
|
||||
css: function(element, name, value) {
|
||||
if (isDefined(value)) {
|
||||
element.style[name] = value;
|
||||
} else {
|
||||
return element.style[name];
|
||||
}
|
||||
},
|
||||
|
||||
attr: function(element, name, value){
|
||||
if (isDefined(value)) {
|
||||
element.setAttribute(name, value);
|
||||
} else if (element.getAttribute) {
|
||||
// the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
|
||||
// some elements (e.g. Document) don't have get attribute, so return undefined
|
||||
return element.getAttribute(name, 2);
|
||||
}
|
||||
},
|
||||
|
||||
text: extend(msie
|
||||
? function(element, value) {
|
||||
// NodeType == 3 is text node
|
||||
if (element.nodeType == 3) {
|
||||
if (isUndefined(value))
|
||||
return element.nodeValue;
|
||||
element.nodeValue = value;
|
||||
} else {
|
||||
if (isUndefined(value))
|
||||
return element.innerText;
|
||||
element.innerText = value;
|
||||
}
|
||||
}
|
||||
: function(element, value) {
|
||||
if (isUndefined(value)) {
|
||||
return element.textContent;
|
||||
}
|
||||
element.textContent = value;
|
||||
}, {$dv:''}),
|
||||
|
||||
val: function(element, value) {
|
||||
if (isUndefined(value)) {
|
||||
return element.value;
|
||||
}
|
||||
element.value = value;
|
||||
},
|
||||
|
||||
html: function(element, value) {
|
||||
if (isUndefined(value)) {
|
||||
return element.innerHTML;
|
||||
}
|
||||
for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
|
||||
JQLiteDealoc(childNodes[i]);
|
||||
}
|
||||
element.innerHTML = value;
|
||||
}
|
||||
}, function(fn, name){
|
||||
/**
|
||||
* Properties: writes return selection, reads return first value
|
||||
*/
|
||||
JQLite.prototype[name] = function(arg1, arg2) {
|
||||
if ((fn.length == 2 ? arg1 : arg2) === undefined) {
|
||||
if (isObject(arg1)) {
|
||||
// we are a write, but the object properties are the key/values
|
||||
for(var i=0; i < this.length; i++) {
|
||||
for ( var key in arg1) {
|
||||
fn(this[i], key, arg1[key]);
|
||||
}
|
||||
}
|
||||
// return self for chaining
|
||||
return this;
|
||||
} else {
|
||||
// we are a read, so read the first child.
|
||||
if (this.length)
|
||||
return fn(this[0], arg1, arg2);
|
||||
}
|
||||
} else {
|
||||
// we are a write, so apply to all children
|
||||
for(var i=0; i < this.length; i++) {
|
||||
fn(this[i], arg1, arg2);
|
||||
}
|
||||
// return self for chaining
|
||||
return this;
|
||||
}
|
||||
return fn.$dv;
|
||||
};
|
||||
});
|
||||
|
||||
//////////////////////////////////////////
|
||||
// Functions iterating traversal.
|
||||
// These functions chain results into a single
|
||||
// selector.
|
||||
//////////////////////////////////////////
|
||||
forEach({
|
||||
removeData: JQLiteRemoveData,
|
||||
|
||||
dealoc: JQLiteDealoc,
|
||||
|
||||
bind: function(element, type, fn){
|
||||
var bind = JQLiteData(element, 'bind'),
|
||||
eventHandler;
|
||||
if (!bind) JQLiteData(element, 'bind', bind = {});
|
||||
forEach(type.split(' '), function(type){
|
||||
eventHandler = bind[type];
|
||||
if (!eventHandler) {
|
||||
bind[type] = eventHandler = function(event) {
|
||||
if (!event.preventDefault) {
|
||||
event.preventDefault = function(){
|
||||
event.returnValue = false; //ie
|
||||
};
|
||||
}
|
||||
if (!event.stopPropagation) {
|
||||
event.stopPropagation = function() {
|
||||
event.cancelBubble = true; //ie
|
||||
};
|
||||
}
|
||||
forEach(eventHandler.fns, function(fn){
|
||||
fn.call(element, event);
|
||||
});
|
||||
};
|
||||
eventHandler.fns = [];
|
||||
addEventListenerFn(element, type, eventHandler);
|
||||
}
|
||||
eventHandler.fns.push(fn);
|
||||
});
|
||||
},
|
||||
|
||||
replaceWith: function(element, replaceNode) {
|
||||
var index, parent = element.parentNode;
|
||||
JQLiteDealoc(element);
|
||||
forEach(new JQLite(replaceNode), function(node){
|
||||
if (index) {
|
||||
parent.insertBefore(node, index.nextSibling);
|
||||
} else {
|
||||
parent.replaceChild(node, element);
|
||||
}
|
||||
index = node;
|
||||
});
|
||||
},
|
||||
|
||||
children: function(element) {
|
||||
var children = [];
|
||||
forEach(element.childNodes, function(element){
|
||||
if (element.nodeName != '#text')
|
||||
children.push(element);
|
||||
});
|
||||
return children;
|
||||
},
|
||||
|
||||
append: function(element, node) {
|
||||
forEach(new JQLite(node), function(child){
|
||||
element.appendChild(child);
|
||||
});
|
||||
},
|
||||
|
||||
remove: function(element) {
|
||||
JQLiteDealoc(element);
|
||||
var parent = element.parentNode;
|
||||
if (parent) parent.removeChild(element);
|
||||
},
|
||||
|
||||
after: function(element, newElement) {
|
||||
var index = element, parent = element.parentNode;
|
||||
forEach(new JQLite(newElement), function(node){
|
||||
parent.insertBefore(node, index.nextSibling);
|
||||
index = node;
|
||||
});
|
||||
},
|
||||
|
||||
addClass: JQLiteAddClass,
|
||||
removeClass: JQLiteRemoveClass,
|
||||
|
||||
toggleClass: function(element, selector, condition) {
|
||||
if (isUndefined(condition)) {
|
||||
condition = !JQLiteHasClass(element, selector);
|
||||
}
|
||||
(condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);
|
||||
},
|
||||
|
||||
parent: function(element) {
|
||||
// in IE it returns undefined, but we need differentiate it from functions which have no return
|
||||
return element.parentNode || null;
|
||||
},
|
||||
|
||||
next: function(element) {
|
||||
return element.nextSibling;
|
||||
},
|
||||
|
||||
find: function(element, selector) {
|
||||
return element.getElementsByTagName(selector);
|
||||
},
|
||||
|
||||
clone: JQLiteClone
|
||||
}, function(fn, name){
|
||||
/**
|
||||
* chaining functions
|
||||
*/
|
||||
JQLite.prototype[name] = function(arg1, arg2) {
|
||||
var value;
|
||||
for(var i=0; i < this.length; i++) {
|
||||
if (value == undefined) {
|
||||
value = fn(this[i], arg1, arg2);
|
||||
if (value !== undefined) {
|
||||
// any function which returns a value needs to be wrapped
|
||||
value = jqLite(value);
|
||||
}
|
||||
} else {
|
||||
JQLiteAddNodes(value, fn(this[i], arg1, arg2));
|
||||
}
|
||||
}
|
||||
return value == undefined ? this : value;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -38,10 +38,10 @@ angularTextMarkup('{{}}', function(text, textNode, parentElement) {
|
||||
forEach(parseBindings(text), function(text){
|
||||
var exp = binding(text);
|
||||
if (exp) {
|
||||
newElement = self.element('span');
|
||||
newElement = jqLite('<span>');
|
||||
newElement.attr('ng:bind', exp);
|
||||
} else {
|
||||
newElement = self.text(text);
|
||||
newElement = jqLite(document.createTextNode(text));
|
||||
}
|
||||
if (msie && text.charAt(0) == ' ') {
|
||||
newElement = jqLite('<span> </span>');
|
||||
@@ -57,12 +57,24 @@ angularTextMarkup('{{}}', function(text, textNode, parentElement) {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: this should be widget not a markup
|
||||
angularTextMarkup('OPTION', function(text, textNode, parentElement){
|
||||
if (nodeName_(parentElement) == "OPTION") {
|
||||
var select = document.createElement('select');
|
||||
select.insertBefore(parentElement[0].cloneNode(true), _null);
|
||||
if (!select.innerHTML.match(/<option(\s.*\s|\s)value\s*=\s*.*>.*<\/\s*option\s*>/gi)) {
|
||||
/**
|
||||
* This tries to normalize the behavior of value attribute across browsers. If value attribute is
|
||||
* not specified, then specify it to be that of the text.
|
||||
*/
|
||||
angularTextMarkup('option', function(text, textNode, parentElement){
|
||||
if (lowercase(nodeName_(parentElement)) == 'option') {
|
||||
if (msie <= 7) {
|
||||
// In IE7 The issue is that there is no way to see if the value was specified hence
|
||||
// we have to resort to parsing HTML;
|
||||
htmlParser(parentElement[0].outerHTML, {
|
||||
start: function(tag, attrs) {
|
||||
if (isUndefined(attrs.value)) {
|
||||
parentElement.attr('value', text);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (parentElement[0].getAttribute('value') == null) {
|
||||
// jQuery does normalization on 'value' so we have to bypass it.
|
||||
parentElement.attr('value', text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,9 +107,9 @@ function lex(text, parseStringsForObjects){
|
||||
function throwError(error, start, end) {
|
||||
end = end || index;
|
||||
throw Error("Lexer Error: " + error + " at column" +
|
||||
(isDefined(start) ?
|
||||
"s " + start + "-" + index + " [" + text.substring(start, end) + "]" :
|
||||
" " + end) +
|
||||
(isDefined(start)
|
||||
? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
|
||||
: " " + end) +
|
||||
" in expression [" + text + "].");
|
||||
}
|
||||
|
||||
@@ -199,8 +199,9 @@ function lex(text, parseStringsForObjects){
|
||||
index++;
|
||||
tokens.push({index:start, text:rawString, string:string, json:true,
|
||||
fn:function(){
|
||||
return (string.length == dateParseLength) ?
|
||||
angular['String']['toDate'](string) : string;
|
||||
return (string.length == dateParseLength)
|
||||
? angular['String']['toDate'](string)
|
||||
: string;
|
||||
}});
|
||||
return;
|
||||
} else {
|
||||
@@ -588,9 +589,9 @@ function parser(text, json){
|
||||
}
|
||||
var fnPtr = fn(self) || noop;
|
||||
// IE stupidity!
|
||||
return fnPtr.apply ?
|
||||
fnPtr.apply(self, args) :
|
||||
fnPtr(args[0], args[1], args[2], args[3], args[4]);
|
||||
return fnPtr.apply
|
||||
? fnPtr.apply(self, args)
|
||||
: fnPtr(args[0], args[1], args[2], args[3], args[4]);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = /^<\s*([\w:]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:]+)[^>]*>/,
|
||||
ATTR_REGEXP = /(\w+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
||||
var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
|
||||
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
||||
BEGIN_TAG_REGEXP = /^</,
|
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
||||
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
||||
@@ -42,15 +42,12 @@ var closeSelfElements = makeMap("colgroup,dd,dt,li,p,td,tfoot,th,thead,tr");
|
||||
var specialElements = makeMap("script,style");
|
||||
var validElements = extend({}, emptyElements, blockElements, inlineElements, closeSelfElements);
|
||||
|
||||
//see: http://www.w3.org/TR/html4/index/attributes.html
|
||||
//Attributes that have their values filled in disabled="disabled"
|
||||
var fillAttrs = makeMap("compact,ismap,nohref,nowrap");
|
||||
//Attributes that have href and hence need to be sanitized
|
||||
var uriAttrs = makeMap("background,href,longdesc,src,usemap");
|
||||
var validAttrs = extend({}, fillAttrs, uriAttrs, makeMap(
|
||||
var validAttrs = extend({}, uriAttrs, makeMap(
|
||||
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
||||
'color,cols,colspan,coords,dir,face,headers,height,hreflang,hspace,'+
|
||||
'lang,language,rel,rev,rows,rowspan,rules,'+
|
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
|
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
|
||||
'scope,scrolling,shape,span,start,summary,target,title,type,'+
|
||||
'valign,value,vspace,width'));
|
||||
|
||||
@@ -81,8 +78,7 @@ function htmlParser( html, handler ) {
|
||||
index = html.indexOf("-->");
|
||||
|
||||
if ( index >= 0 ) {
|
||||
if ( handler.comment )
|
||||
handler.comment( html.substring( 4, index ) );
|
||||
if (handler.comment) handler.comment( html.substring( 4, index ) );
|
||||
html = html.substring( index + 3 );
|
||||
chars = false;
|
||||
}
|
||||
@@ -114,7 +110,7 @@ function htmlParser( html, handler ) {
|
||||
var text = index < 0 ? html : html.substring( 0, index );
|
||||
html = index < 0 ? "" : html.substring( index );
|
||||
|
||||
handler.chars( decodeEntities(text) );
|
||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -123,7 +119,7 @@ function htmlParser( html, handler ) {
|
||||
replace(COMMENT_REGEXP, "$1").
|
||||
replace(CDATA_REGEXP, "$1");
|
||||
|
||||
handler.chars( decodeEntities(text) );
|
||||
if (handler.chars) handler.chars( decodeEntities(text) );
|
||||
|
||||
return "";
|
||||
});
|
||||
@@ -159,16 +155,15 @@ function htmlParser( html, handler ) {
|
||||
|
||||
var attrs = {};
|
||||
|
||||
rest.replace(ATTR_REGEXP, function(match, name) {
|
||||
var value = arguments[2] ? arguments[2] :
|
||||
arguments[3] ? arguments[3] :
|
||||
arguments[4] ? arguments[4] :
|
||||
fillAttrs[name] ? name : "";
|
||||
rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
|
||||
var value = doubleQuotedValue
|
||||
|| singleQoutedValue
|
||||
|| unqoutedValue
|
||||
|| '';
|
||||
|
||||
attrs[name] = decodeEntities(value); //value.replace(/(^|[^\\])"/g, '$1\\\"') //"
|
||||
attrs[name] = decodeEntities(value);
|
||||
});
|
||||
|
||||
handler.start( tagName, attrs, unary );
|
||||
if (handler.start) handler.start( tagName, attrs, unary );
|
||||
}
|
||||
|
||||
function parseEndTag( tag, tagName ) {
|
||||
@@ -183,7 +178,7 @@ function htmlParser( html, handler ) {
|
||||
if ( pos >= 0 ) {
|
||||
// Close all the open elements, up the stack
|
||||
for ( i = stack.length - 1; i >= pos; i-- )
|
||||
handler.end( stack[ i ] );
|
||||
if (handler.end) handler.end( stack[ i ] );
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos;
|
||||
@@ -210,7 +205,7 @@ function makeMap(str){
|
||||
var hiddenPre=document.createElement("pre");
|
||||
function decodeEntities(value) {
|
||||
hiddenPre.innerHTML=value.replace(/</g,"<");
|
||||
return hiddenPre.innerText || hiddenPre.textContent;
|
||||
return hiddenPre.innerText || hiddenPre.textContent || '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -256,6 +256,12 @@ function browserTrigger(element, type) {
|
||||
element.checked = !element.checked;
|
||||
break;
|
||||
}
|
||||
// WTF!!! Error: Unspecified error.
|
||||
// Don't know why, but some elements when detached seem to be in inconsistent state and
|
||||
// calling .fireEvent() on them will result in very unhelpful error (Error: Unspecified error)
|
||||
// forcing the browser to compute the element position (by reading its CSS)
|
||||
// puts the element in consistent state.
|
||||
element.style.posLeft;
|
||||
element.fireEvent('on' + type);
|
||||
if (lowercase(element.type) == 'submit') {
|
||||
while(element) {
|
||||
@@ -303,9 +309,9 @@ function browserTrigger(element, type) {
|
||||
*/
|
||||
_jQuery.fn.bindings = function(name) {
|
||||
function contains(text, value) {
|
||||
return value instanceof RegExp ?
|
||||
value.test(text) :
|
||||
text && text.indexOf(value) >= 0;
|
||||
return value instanceof RegExp
|
||||
? value.test(text)
|
||||
: text && text.indexOf(value) >= 0;
|
||||
}
|
||||
var result = [];
|
||||
this.find('.ng-binding:visible').each(function() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var $scenario = new angular.scenario.Runner(window);
|
||||
|
||||
jqLite(document).ready(function() {
|
||||
jqLiteWrap(document).ready(function() {
|
||||
angularScenarioInit($scenario, angularJsConfig(document));
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$cookieStore
|
||||
* @requires $cookies
|
||||
*
|
||||
* @description
|
||||
* Provides a key-value (string-object) storage, that is backed by session cookies.
|
||||
* Objects put or retrieved from this storage are automatically serialized or
|
||||
* deserialized by angular's toJson/fromJson.
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$cookieStore', function($store) {
|
||||
|
||||
return {
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$cookieStore#get
|
||||
* @methodOf angular.service.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Returns the value of given cookie key
|
||||
*
|
||||
* @param {string} key Id to use for lookup.
|
||||
* @returns {Object} Deserialized cookie value.
|
||||
*/
|
||||
get: function(key) {
|
||||
return fromJson($store[key]);
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$cookieStore#put
|
||||
* @methodOf angular.service.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Sets a value for given cookie key
|
||||
*
|
||||
* @param {string} key Id for the `value`.
|
||||
* @param {Object} value Value to be stored.
|
||||
*/
|
||||
put: function(key, value) {
|
||||
$store[key] = toJson(value);
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$cookieStore#remove
|
||||
* @methodOf angular.service.$cookieStore
|
||||
*
|
||||
* @description
|
||||
* Remove given cookie
|
||||
*
|
||||
* @param {string} key Id of the key-value pair to delete.
|
||||
*/
|
||||
remove: function(key) {
|
||||
delete $store[key];
|
||||
}
|
||||
};
|
||||
|
||||
}, ['$cookies']);
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$cookies
|
||||
* @requires $browser
|
||||
*
|
||||
* @description
|
||||
* Provides read/write access to browser's cookies.
|
||||
*
|
||||
* Only a simple Object is exposed and by adding or removing properties to/from
|
||||
* this object, new cookies are created/deleted at the end of current $eval.
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$cookies', function($browser) {
|
||||
var rootScope = this,
|
||||
cookies = {},
|
||||
lastCookies = {},
|
||||
lastBrowserCookies,
|
||||
runEval = false;
|
||||
|
||||
//creates a poller fn that copies all cookies from the $browser to service & inits the service
|
||||
$browser.addPollFn(function() {
|
||||
var currentCookies = $browser.cookies();
|
||||
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
|
||||
lastBrowserCookies = currentCookies;
|
||||
copy(currentCookies, lastCookies);
|
||||
copy(currentCookies, cookies);
|
||||
if (runEval) rootScope.$eval();
|
||||
}
|
||||
})();
|
||||
|
||||
runEval = true;
|
||||
|
||||
//at the end of each eval, push cookies
|
||||
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
|
||||
// strings or browser refuses to store some cookies, we update the model in the push fn.
|
||||
this.$onEval(PRIORITY_LAST, push);
|
||||
|
||||
return cookies;
|
||||
|
||||
|
||||
/**
|
||||
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
|
||||
*/
|
||||
function push(){
|
||||
var name,
|
||||
value,
|
||||
browserCookies,
|
||||
updated;
|
||||
|
||||
//delete any cookies deleted in $cookies
|
||||
for (name in lastCookies) {
|
||||
if (isUndefined(cookies[name])) {
|
||||
$browser.cookies(name, _undefined);
|
||||
}
|
||||
}
|
||||
|
||||
//update all cookies updated in $cookies
|
||||
for(name in cookies) {
|
||||
value = cookies[name];
|
||||
if (!isString(value)) {
|
||||
if (isDefined(lastCookies[name])) {
|
||||
cookies[name] = lastCookies[name];
|
||||
} else {
|
||||
delete cookies[name];
|
||||
}
|
||||
} else if (value !== lastCookies[name]) {
|
||||
$browser.cookies(name, value);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
//verify what was actually stored
|
||||
if (updated){
|
||||
updated = false;
|
||||
browserCookies = $browser.cookies();
|
||||
|
||||
for (name in cookies) {
|
||||
if (cookies[name] !== browserCookies[name]) {
|
||||
//delete or reset all cookies that the browser dropped from $cookies
|
||||
if (isUndefined(browserCookies[name])) {
|
||||
delete cookies[name];
|
||||
} else {
|
||||
cookies[name] = browserCookies[name];
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ['$browser']);
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$defer
|
||||
* @requires $browser
|
||||
* @requires $exceptionHandler
|
||||
* @requires $updateView
|
||||
*
|
||||
* @description
|
||||
* Delegates to {@link angular.service.$browser.defer $browser.defer}, but wraps the `fn` function
|
||||
* into a try/catch block and delegates any exceptions to
|
||||
* {@link angular.services.$exceptionHandler $exceptionHandler} service.
|
||||
*
|
||||
* In tests you can use `$browser.defer.flush()` to flush the queue of deferred functions.
|
||||
*
|
||||
* @param {function()} fn A function, who's execution should be deferred.
|
||||
* @param {number=} [delay=0] of milliseconds to defer the function execution.
|
||||
*/
|
||||
angularServiceInject('$defer', function($browser, $exceptionHandler, $updateView) {
|
||||
return function(fn, delay) {
|
||||
$browser.defer(function() {
|
||||
try {
|
||||
fn();
|
||||
} catch(e) {
|
||||
$exceptionHandler(e);
|
||||
} finally {
|
||||
$updateView();
|
||||
}
|
||||
}, delay);
|
||||
};
|
||||
}, ['$browser', '$exceptionHandler', '$updateView']);
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$document
|
||||
* @requires $window
|
||||
*
|
||||
* @description
|
||||
* A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
|
||||
* element.
|
||||
*/
|
||||
angularServiceInject("$document", function(window){
|
||||
return jqLite(window.document);
|
||||
}, ['$window']);
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$exceptionHandler
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
* Any uncaught exception in angular expressions is delegated to this service.
|
||||
* The default implementation simply delegates to `$log.error` which logs it into
|
||||
* the browser console.
|
||||
*
|
||||
* In unit tests, if `angular-mocks.js` is loaded, this service is overriden by
|
||||
* {@link angular.mock.service.$exceptionHandler mock $exceptionHandler}
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
var $exceptionHandlerFactory; //reference to be used only in tests
|
||||
angularServiceInject('$exceptionHandler', $exceptionHandlerFactory = function($log){
|
||||
return function(e) {
|
||||
$log.error(e);
|
||||
};
|
||||
}, ['$log']);
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$hover
|
||||
* @requires $browser
|
||||
* @requires $document
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject("$hover", function(browser, document) {
|
||||
var tooltip, self = this, error, width = 300, arrowWidth = 10, body = jqLite(document[0].body);
|
||||
browser.hover(function(element, show){
|
||||
if (show && (error = element.attr(NG_EXCEPTION) || element.attr(NG_VALIDATION_ERROR))) {
|
||||
if (!tooltip) {
|
||||
tooltip = {
|
||||
callout: jqLite('<div id="ng-callout"></div>'),
|
||||
arrow: jqLite('<div></div>'),
|
||||
title: jqLite('<div class="ng-title"></div>'),
|
||||
content: jqLite('<div class="ng-content"></div>')
|
||||
};
|
||||
tooltip.callout.append(tooltip.arrow);
|
||||
tooltip.callout.append(tooltip.title);
|
||||
tooltip.callout.append(tooltip.content);
|
||||
body.append(tooltip.callout);
|
||||
}
|
||||
var docRect = body[0].getBoundingClientRect(),
|
||||
elementRect = element[0].getBoundingClientRect(),
|
||||
leftSpace = docRect.right - elementRect.right - arrowWidth;
|
||||
tooltip.title.text(element.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error...");
|
||||
tooltip.content.text(error);
|
||||
if (leftSpace < width) {
|
||||
tooltip.arrow.addClass('ng-arrow-right');
|
||||
tooltip.arrow.css({left: (width + 1)+'px'});
|
||||
tooltip.callout.css({
|
||||
position: 'fixed',
|
||||
left: (elementRect.left - arrowWidth - width - 4) + "px",
|
||||
top: (elementRect.top - 3) + "px",
|
||||
width: width + "px"
|
||||
});
|
||||
} else {
|
||||
tooltip.arrow.addClass('ng-arrow-left');
|
||||
tooltip.callout.css({
|
||||
position: 'fixed',
|
||||
left: (elementRect.right + arrowWidth) + "px",
|
||||
top: (elementRect.top - 3) + "px",
|
||||
width: width + "px"
|
||||
});
|
||||
}
|
||||
} else if (tooltip) {
|
||||
tooltip.callout.remove();
|
||||
tooltip = _null;
|
||||
}
|
||||
});
|
||||
}, ['$browser', '$document'], true);
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$invalidWidgets
|
||||
*
|
||||
* @description
|
||||
* Keeps references to all invalid widgets found during validation.
|
||||
* Can be queried to find whether there are any invalid widgets currently displayed.
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject("$invalidWidgets", function(){
|
||||
var invalidWidgets = [];
|
||||
|
||||
|
||||
/** Remove an element from the array of invalid widgets */
|
||||
invalidWidgets.markValid = function(element){
|
||||
var index = indexOf(invalidWidgets, element);
|
||||
if (index != -1)
|
||||
invalidWidgets.splice(index, 1);
|
||||
};
|
||||
|
||||
|
||||
/** Add an element to the array of invalid widgets */
|
||||
invalidWidgets.markInvalid = function(element){
|
||||
var index = indexOf(invalidWidgets, element);
|
||||
if (index === -1)
|
||||
invalidWidgets.push(element);
|
||||
};
|
||||
|
||||
|
||||
/** Return count of all invalid widgets that are currently visible */
|
||||
invalidWidgets.visible = function() {
|
||||
var count = 0;
|
||||
forEach(invalidWidgets, function(widget){
|
||||
count = count + (isVisible(widget) ? 1 : 0);
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
|
||||
/* At the end of each eval removes all invalid widgets that are not part of the current DOM. */
|
||||
this.$onEval(PRIORITY_LAST, function() {
|
||||
for(var i = 0; i < invalidWidgets.length;) {
|
||||
var widget = invalidWidgets[i];
|
||||
if (isOrphan(widget[0])) {
|
||||
invalidWidgets.splice(i, 1);
|
||||
if (widget.dealoc) widget.dealoc();
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Traverses DOM element's (widget's) parents and considers the element to be an orphant if one of
|
||||
* it's parents isn't the current window.document.
|
||||
*/
|
||||
function isOrphan(widget) {
|
||||
if (widget == window.document) return false;
|
||||
var parent = widget.parentNode;
|
||||
return !parent || isOrphan(parent);
|
||||
}
|
||||
|
||||
return invalidWidgets;
|
||||
});
|
||||
@@ -0,0 +1,264 @@
|
||||
var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,
|
||||
HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/,
|
||||
DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$location
|
||||
* @requires $browser
|
||||
*
|
||||
* @property {string} href
|
||||
* @property {string} protocol
|
||||
* @property {string} host
|
||||
* @property {number} port
|
||||
* @property {string} path
|
||||
* @property {Object.<string|boolean>} search
|
||||
* @property {string} hash
|
||||
* @property {string} hashPath
|
||||
* @property {Object.<string|boolean>} hashSearch
|
||||
*
|
||||
* @description
|
||||
* Parses the browser location url and makes it available to your application.
|
||||
* Any changes to the url are reflected into $location service and changes to
|
||||
* $location are reflected to url.
|
||||
* Notice that using browser's forward/back buttons changes the $location.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<a href="#">clear hash</a> |
|
||||
<a href="#myPath?name=misko">test hash</a><br/>
|
||||
<input type='text' name="$location.hash"/>
|
||||
<pre>$location = {{$location}}</pre>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularServiceInject("$location", function($browser) {
|
||||
var scope = this,
|
||||
location = {update:update, updateHash: updateHash},
|
||||
lastLocation = {};
|
||||
|
||||
$browser.onHashChange(function() { //register
|
||||
update($browser.getUrl());
|
||||
copy(location, lastLocation);
|
||||
scope.$eval();
|
||||
})(); //initialize
|
||||
|
||||
this.$onEval(PRIORITY_FIRST, sync);
|
||||
this.$onEval(PRIORITY_LAST, updateBrowser);
|
||||
|
||||
return location;
|
||||
|
||||
// PUBLIC METHODS
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#update
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Update location object
|
||||
* Does not immediately update the browser
|
||||
* Browser is updated at the end of $eval()
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
scope.$location.update('http://www.angularjs.org/path#hash?search=x');
|
||||
scope.$location.update({host: 'www.google.com', protocol: 'https'});
|
||||
scope.$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}});
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*
|
||||
* @param {(string|Object)} href Full href as a string or object with properties
|
||||
*/
|
||||
function update(href) {
|
||||
if (isString(href)) {
|
||||
extend(location, parseHref(href));
|
||||
} else {
|
||||
if (isDefined(href.hash)) {
|
||||
extend(href, isString(href.hash) ? parseHash(href.hash) : href.hash);
|
||||
}
|
||||
|
||||
extend(location, href);
|
||||
|
||||
if (isDefined(href.hashPath || href.hashSearch)) {
|
||||
location.hash = composeHash(location);
|
||||
}
|
||||
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$location#updateHash
|
||||
* @methodOf angular.service.$location
|
||||
*
|
||||
* @description
|
||||
* Update location hash part
|
||||
* @see update()
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
scope.$location.updateHash('/hp')
|
||||
==> update({hashPath: '/hp'})
|
||||
scope.$location.updateHash({a: true, b: 'val'})
|
||||
==> update({hashSearch: {a: true, b: 'val'}})
|
||||
scope.$location.updateHash('/hp', {a: true})
|
||||
==> update({hashPath: '/hp', hashSearch: {a: true}})
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*
|
||||
* @param {(string|Object)} path A hashPath or hashSearch object
|
||||
* @param {Object=} search A hashSearch object
|
||||
*/
|
||||
function updateHash(path, search) {
|
||||
var hash = {};
|
||||
|
||||
if (isString(path)) {
|
||||
hash.hashPath = path;
|
||||
hash.hashSearch = search || {};
|
||||
} else
|
||||
hash.hashSearch = path;
|
||||
|
||||
hash.hash = composeHash(hash);
|
||||
|
||||
update({hash: hash});
|
||||
}
|
||||
|
||||
|
||||
// INNER METHODS
|
||||
|
||||
/**
|
||||
* Synchronizes all location object properties.
|
||||
*
|
||||
* User is allowed to change properties, so after property change,
|
||||
* location object is not in consistent state.
|
||||
*
|
||||
* Properties are synced with the following precedence order:
|
||||
*
|
||||
* - `$location.href`
|
||||
* - `$location.hash`
|
||||
* - everything else
|
||||
*
|
||||
* @example
|
||||
* <pre>
|
||||
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
|
||||
* </pre>
|
||||
* immediately after this call, other properties are still the old ones...
|
||||
*
|
||||
* This method checks the changes and update location to the consistent state
|
||||
*/
|
||||
function sync() {
|
||||
if (!equals(location, lastLocation)) {
|
||||
if (location.href != lastLocation.href) {
|
||||
update(location.href);
|
||||
return;
|
||||
}
|
||||
if (location.hash != lastLocation.hash) {
|
||||
var hash = parseHash(location.hash);
|
||||
updateHash(hash.hashPath, hash.hashSearch);
|
||||
} else {
|
||||
location.hash = composeHash(location);
|
||||
location.href = composeHref(location);
|
||||
}
|
||||
update(location.href);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If location has changed, update the browser
|
||||
* This method is called at the end of $eval() phase
|
||||
*/
|
||||
function updateBrowser() {
|
||||
sync();
|
||||
|
||||
if ($browser.getUrl() != location.href) {
|
||||
$browser.setUrl(location.href);
|
||||
copy(location, lastLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose href string from a location object
|
||||
*
|
||||
* @param {Object} loc The location object with all properties
|
||||
* @return {string} Composed href
|
||||
*/
|
||||
function composeHref(loc) {
|
||||
var url = toKeyValue(loc.search);
|
||||
var port = (loc.port == DEFAULT_PORTS[loc.protocol] ? _null : loc.port);
|
||||
|
||||
return loc.protocol + '://' + loc.host +
|
||||
(port ? ':' + port : '') + loc.path +
|
||||
(url ? '?' + url : '') + (loc.hash ? '#' + loc.hash : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose hash string from location object
|
||||
*
|
||||
* @param {Object} loc Object with hashPath and hashSearch properties
|
||||
* @return {string} Hash string
|
||||
*/
|
||||
function composeHash(loc) {
|
||||
var hashSearch = toKeyValue(loc.hashSearch);
|
||||
//TODO: temporary fix for issue #158
|
||||
return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') +
|
||||
(hashSearch ? '?' + hashSearch : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse href string into location object
|
||||
*
|
||||
* @param {string} href
|
||||
* @return {Object} The location object
|
||||
*/
|
||||
function parseHref(href) {
|
||||
var loc = {};
|
||||
var match = URL_MATCH.exec(href);
|
||||
|
||||
if (match) {
|
||||
loc.href = href.replace(/#$/, '');
|
||||
loc.protocol = match[1];
|
||||
loc.host = match[3] || '';
|
||||
loc.port = match[5] || DEFAULT_PORTS[loc.protocol] || _null;
|
||||
loc.path = match[6] || '';
|
||||
loc.search = parseKeyValue(match[8]);
|
||||
loc.hash = match[10] || '';
|
||||
|
||||
extend(loc, parseHash(loc.hash));
|
||||
}
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse hash string into object
|
||||
*
|
||||
* @param {string} hash
|
||||
*/
|
||||
function parseHash(hash) {
|
||||
var h = {};
|
||||
var match = HASH_MATCH.exec(hash);
|
||||
|
||||
if (match) {
|
||||
h.hash = hash;
|
||||
h.hashPath = unescape(match[1] || '');
|
||||
h.hashSearch = parseKeyValue(match[3]);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
}, ['$browser']);
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$log
|
||||
* @requires $window
|
||||
*
|
||||
* @description
|
||||
* Simple service for logging. Default implementation writes the message
|
||||
* into the browser's console (if present).
|
||||
*
|
||||
* The main purpose of this service is to simplify debugging and troubleshooting.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<p>Reload this page with open console, enter text and hit the log button...</p>
|
||||
Message:
|
||||
<input type="text" name="message" value="Hello World!"/>
|
||||
<button ng:click="$log.log(message)">log</button>
|
||||
<button ng:click="$log.warn(message)">warn</button>
|
||||
<button ng:click="$log.info(message)">info</button>
|
||||
<button ng:click="$log.error(message)">error</button>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
var $logFactory; //reference to be used only in tests
|
||||
angularServiceInject("$log", $logFactory = function($window){
|
||||
return {
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#log
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write a log message
|
||||
*/
|
||||
log: consoleLog('log'),
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#warn
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write a warning message
|
||||
*/
|
||||
warn: consoleLog('warn'),
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#info
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write an information message
|
||||
*/
|
||||
info: consoleLog('info'),
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$log#error
|
||||
* @methodOf angular.service.$log
|
||||
*
|
||||
* @description
|
||||
* Write an error message
|
||||
*/
|
||||
error: consoleLog('error')
|
||||
};
|
||||
|
||||
function consoleLog(type) {
|
||||
var console = $window.console || {};
|
||||
var logFn = console[type] || console.log || noop;
|
||||
if (logFn.apply) {
|
||||
return function(){
|
||||
var args = [];
|
||||
forEach(arguments, function(arg){
|
||||
args.push(formatError(arg));
|
||||
});
|
||||
return logFn.apply(console, args);
|
||||
};
|
||||
} else {
|
||||
// we are IE, in which case there is nothing we can do
|
||||
return logFn;
|
||||
}
|
||||
}
|
||||
}, ['$window']);
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$resource
|
||||
* @requires $xhr.cache
|
||||
*
|
||||
* @description
|
||||
* A factory which creates a resource object that lets you interact with
|
||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
||||
*
|
||||
* The returned resource object has action methods which provide high-level behaviors without
|
||||
* the need to interact with the low level {@link angular.service.$xhr $xhr} service or
|
||||
* raw XMLHttpRequest.
|
||||
*
|
||||
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
|
||||
* `/user/:username`.
|
||||
*
|
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
||||
* `actions` methods.
|
||||
*
|
||||
* Each key value in the parameter object is first bound to url template if present and then any
|
||||
* excess keys are appended to the url search query after the `?`.
|
||||
*
|
||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
|
||||
* URL `/path/greet?salutation=Hello`.
|
||||
*
|
||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from
|
||||
* the data object (useful for non-GET operations).
|
||||
*
|
||||
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
|
||||
* default set of resource actions. The declaration should be created in the following format:
|
||||
*
|
||||
* {action1: {method:?, params:?, isArray:?, verifyCache:?},
|
||||
* action2: {method:?, params:?, isArray:?, verifyCache:?},
|
||||
* ...}
|
||||
*
|
||||
* Where:
|
||||
*
|
||||
* - `action` – {string} – The name of action. This name becomes the name of the method on your
|
||||
* resource object.
|
||||
* - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
|
||||
* and `JSON` (also known as JSONP).
|
||||
* - `params` – {object=} – Optional set of pre-bound parameters for this action.
|
||||
* - isArray – {boolean=} – If true then the returned object for this action is an array, see
|
||||
* `returns` section.
|
||||
* - verifyCache – {boolean=} – If true then whenever cache hit occurs, the object is returned and
|
||||
* an async request will be made to the server and the resources as well as the cache will be
|
||||
* updated when the response is received.
|
||||
*
|
||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
||||
* optionally extended with custom `actions`. The default set contains these actions:
|
||||
*
|
||||
* { 'get': {method:'GET'},
|
||||
* 'save': {method:'POST'},
|
||||
* 'query': {method:'GET', isArray:true},
|
||||
* 'remove': {method:'DELETE'},
|
||||
* 'delete': {method:'DELETE'} };
|
||||
*
|
||||
* Calling these methods invoke an {@link angular.service.$xhr} with the specified http method,
|
||||
* destination and parameters. When the data is returned from the server then the object is an
|
||||
* instance of the resource class `save`, `remove` and `delete` actions are available on it as
|
||||
* methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
|
||||
* update, delete) on server-side data like this:
|
||||
* <pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function(){
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It is important to realize that invoking a $resource object method immediately returns an
|
||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the
|
||||
* server the existing reference is populated with the actual data. This is a useful trick since
|
||||
* usually the resource is assigned to a model which is then rendered by the view. Having an empty
|
||||
* object results in no rendering, once the data arrives from the server then the object is
|
||||
* populated with the data and the view automatically re-renders itself showing the new data. This
|
||||
* means that in most case one never has to write a callback function for the action methods.
|
||||
*
|
||||
* The action methods on the class object or instance object can be invoked with the following
|
||||
* parameters:
|
||||
*
|
||||
* - HTTP GET "class" actions: `Resource.action([parameters], [callback])`
|
||||
* - non-GET "class" actions: `Resource.action(postData, [parameters], [callback])`
|
||||
* - non-GET instance actions: `instance.$action([parameters], [callback])`
|
||||
*
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* # Credit card resource
|
||||
*
|
||||
* <pre>
|
||||
// Define CreditCard class
|
||||
var CreditCard = $resource('/user/:userId/card/:cardId',
|
||||
{userId:123, cardId:'@id'}, {
|
||||
charge: {method:'POST', params:{charge:true}}
|
||||
});
|
||||
|
||||
// We can retrieve a collection from the server
|
||||
var cards = CreditCard.query();
|
||||
// GET: /user/123/card
|
||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
||||
|
||||
var card = cards[0];
|
||||
// each item is an instance of CreditCard
|
||||
expect(card instanceof CreditCard).toEqual(true);
|
||||
card.name = "J. Smith";
|
||||
// non GET methods are mapped onto the instances
|
||||
card.$save();
|
||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// our custom method is mapped as well.
|
||||
card.$charge({amount:9.99});
|
||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// we can create an instance as well
|
||||
var newCard = new CreditCard({number:'0123'});
|
||||
newCard.name = "Mike Smith";
|
||||
newCard.$save();
|
||||
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
||||
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
|
||||
expect(newCard.id).toEqual(789);
|
||||
* </pre>
|
||||
*
|
||||
* The object returned from this function execution is a resource "class" which has "static" method
|
||||
* for each action in the definition.
|
||||
*
|
||||
* Calling these methods invoke `$xhr` on the `url` template with the given `method` and `params`.
|
||||
* When the data is returned from the server then the object is an instance of the resource type and
|
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
||||
* operations (create, read, update, delete) on server-side data.
|
||||
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
var user = User.get({userId:123}, function(){
|
||||
user.abc = true;
|
||||
user.$save();
|
||||
});
|
||||
</pre>
|
||||
*
|
||||
* It's worth noting that the callback for `get`, `query` and other method gets passed in the
|
||||
* response that came from the server, so one could rewrite the above example as:
|
||||
*
|
||||
<pre>
|
||||
var User = $resource('/user/:userId', {userId:'@id'});
|
||||
User.get({userId:123}, function(u){
|
||||
u.abc = true;
|
||||
u.$save();
|
||||
});
|
||||
</pre>
|
||||
|
||||
* # Buzz client
|
||||
|
||||
Let's look at what a buzz client created with the `$resource` service looks like:
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
function BuzzController($resource) {
|
||||
this.Activity = $resource(
|
||||
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
|
||||
{alt:'json', callback:'JSON_CALLBACK'},
|
||||
{get:{method:'JSON', params:{visibility:'@self'}}, replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}}
|
||||
);
|
||||
}
|
||||
|
||||
BuzzController.prototype = {
|
||||
fetch: function() {
|
||||
this.activities = this.Activity.get({userId:this.userId});
|
||||
},
|
||||
expandReplies: function(activity) {
|
||||
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
|
||||
}
|
||||
};
|
||||
BuzzController.$inject = ['$resource'];
|
||||
</script>
|
||||
|
||||
<div ng:controller="BuzzController">
|
||||
<input name="userId" value="googlebuzz"/>
|
||||
<button ng:click="fetch()">fetch</button>
|
||||
<hr/>
|
||||
<div ng:repeat="item in activities.data.items">
|
||||
<h1 style="font-size: 15px;">
|
||||
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
|
||||
<a href ng:click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
|
||||
</h1>
|
||||
{{item.object.content | html}}
|
||||
<div ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
|
||||
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
|
||||
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularServiceInject('$resource', function($xhr){
|
||||
var resource = new ResourceFactory($xhr);
|
||||
return bind(resource, resource.route);
|
||||
}, ['$xhr.cache']);
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$route
|
||||
* @requires $location
|
||||
*
|
||||
* @property {Object} current Reference to the current route definition.
|
||||
* @property {Array.<Object>} routes Array of all configured routes.
|
||||
*
|
||||
* @description
|
||||
* Watches `$location.hashPath` and tries to map the hash to an existing route
|
||||
* definition. It is used for deep-linking URLs to controllers and views (HTML partials).
|
||||
*
|
||||
* The `$route` service is typically used in conjunction with {@link angular.widget.ng:view ng:view}
|
||||
* widget.
|
||||
*
|
||||
* @example
|
||||
This example shows how changing the URL hash causes the <tt>$route</tt>
|
||||
to match a route against the URL, and the <tt>[[ng:include]]</tt> pulls in the partial.
|
||||
Try changing the URL in the input box to see changes.
|
||||
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<script>
|
||||
angular.service('myApp', function($route) {
|
||||
$route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl});
|
||||
$route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl});
|
||||
$route.onChange(function() {
|
||||
$route.current.scope.params = $route.current.params;
|
||||
});
|
||||
}, {$inject: ['$route']});
|
||||
|
||||
function BookCntl() {
|
||||
this.name = "BookCntl";
|
||||
}
|
||||
|
||||
function ChapterCntl() {
|
||||
this.name = "ChapterCntl";
|
||||
}
|
||||
</script>
|
||||
|
||||
Chose:
|
||||
<a href="#/Book/Moby">Moby</a> |
|
||||
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
|
||||
<a href="#/Book/Gatsby">Gatsby</a> |
|
||||
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
|
||||
<input type="text" name="$location.hashPath" size="80" />
|
||||
<pre>$location={{$location}}</pre>
|
||||
<pre>$route.current.template={{$route.current.template}}</pre>
|
||||
<pre>$route.current.params={{$route.current.params}}</pre>
|
||||
<pre>$route.current.scope.name={{$route.current.scope.name}}</pre>
|
||||
<hr/>
|
||||
<ng:include src="$route.current.template" scope="$route.current.scope"/>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularServiceInject('$route', function(location, $updateView) {
|
||||
var routes = {},
|
||||
onChange = [],
|
||||
matcher = switchRouteMatcher,
|
||||
parentScope = this,
|
||||
dirty = 0,
|
||||
$route = {
|
||||
routes: routes,
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$route#onChange
|
||||
* @methodOf angular.service.$route
|
||||
*
|
||||
* @param {function()} fn Function that will be called when `$route.current` changes.
|
||||
* @returns {function()} The registered function.
|
||||
*
|
||||
* @description
|
||||
* Register a handler function that will be called when route changes
|
||||
*/
|
||||
onChange: function(fn) {
|
||||
onChange.push(fn);
|
||||
return fn;
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$route#parent
|
||||
* @methodOf angular.service.$route
|
||||
*
|
||||
* @param {Scope} [scope=rootScope] Scope to be used as parent for newly created
|
||||
* `$route.current.scope` scopes.
|
||||
*
|
||||
* @description
|
||||
* Sets a scope to be used as the parent scope for scopes created on route change. If not
|
||||
* set, defaults to the root scope.
|
||||
*/
|
||||
parent: function(scope) {
|
||||
if (scope) parentScope = scope;
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$route#when
|
||||
* @methodOf angular.service.$route
|
||||
*
|
||||
* @param {string} path Route path (matched against `$location.hash`)
|
||||
* @param {Object} params Mapping information to be assigned to `$route.current` on route
|
||||
* match.
|
||||
*
|
||||
* Object properties:
|
||||
*
|
||||
* - `controller` – `{function()=}` – Controller fn that should be associated with newly
|
||||
* created scope.
|
||||
* - `template` – `{string=}` – path to an html template that should be used by
|
||||
* {@link angular.widget.ng:view ng:view} or
|
||||
* {@link angular.widget.ng:include ng:include} widgets.
|
||||
* - `redirectTo` – {(string|function())=} – value to update
|
||||
* {@link angular.service.$location $location} hash with and trigger route redirection.
|
||||
*
|
||||
* If `redirectTo` is a function, it will be called with the following parameters:
|
||||
*
|
||||
* - `{Object.<string>}` - route parameters extracted from the current
|
||||
* `$location.hashPath` by applying the current route template.
|
||||
* - `{string}` - current `$location.hash`
|
||||
* - `{string}` - current `$location.hashPath`
|
||||
* - `{string}` - current `$location.hashSearch`
|
||||
*
|
||||
* The custom `redirectTo` function is expected to return a string which will be used
|
||||
* to update `$location.hash`.
|
||||
*
|
||||
* @returns {Object} route object
|
||||
*
|
||||
* @description
|
||||
* Adds a new route definition to the `$route` service.
|
||||
*/
|
||||
when:function (path, params) {
|
||||
if (isUndefined(path)) return routes; //TODO(im): remove - not needed!
|
||||
var route = routes[path];
|
||||
if (!route) route = routes[path] = {};
|
||||
if (params) extend(route, params);
|
||||
dirty++;
|
||||
return route;
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$route#otherwise
|
||||
* @methodOf angular.service.$route
|
||||
*
|
||||
* @description
|
||||
* Sets route definition that will be used on route change when no other route definition
|
||||
* is matched.
|
||||
*
|
||||
* @param {Object} params Mapping information to be assigned to `$route.current`.
|
||||
*/
|
||||
otherwise: function(params) {
|
||||
$route.when(null, params);
|
||||
},
|
||||
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc method
|
||||
* @name angular.service.$route#reload
|
||||
* @methodOf angular.service.$route
|
||||
*
|
||||
* @description
|
||||
* Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next
|
||||
* eval even if {@link angular.service.$location $location} hasn't changed.
|
||||
*/
|
||||
reload: function() {
|
||||
dirty++;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function switchRouteMatcher(on, when, dstName) {
|
||||
var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$',
|
||||
params = [],
|
||||
dst = {};
|
||||
forEach(when.split(/\W/), function(param){
|
||||
if (param) {
|
||||
var paramRegExp = new RegExp(":" + param + "([\\W])");
|
||||
if (regex.match(paramRegExp)) {
|
||||
regex = regex.replace(paramRegExp, "([^\/]*)$1");
|
||||
params.push(param);
|
||||
}
|
||||
}
|
||||
});
|
||||
var match = on.match(new RegExp(regex));
|
||||
if (match) {
|
||||
forEach(params, function(name, index){
|
||||
dst[name] = match[index + 1];
|
||||
});
|
||||
if (dstName) this.$set(dstName, dst);
|
||||
}
|
||||
return match ? dst : _null;
|
||||
}
|
||||
|
||||
|
||||
function updateRoute(){
|
||||
var childScope, routeParams, pathParams, segmentMatch, key, redir;
|
||||
|
||||
$route.current = _null;
|
||||
forEach(routes, function(rParams, rPath) {
|
||||
if (!pathParams) {
|
||||
if (pathParams = matcher(location.hashPath, rPath)) {
|
||||
routeParams = rParams;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// "otherwise" fallback
|
||||
routeParams = routeParams || routes[_null];
|
||||
|
||||
if(routeParams) {
|
||||
if (routeParams.redirectTo) {
|
||||
if (isString(routeParams.redirectTo)) {
|
||||
// interpolate the redirectTo string
|
||||
redir = {hashPath: '',
|
||||
hashSearch: extend({}, location.hashSearch, pathParams)};
|
||||
|
||||
forEach(routeParams.redirectTo.split(':'), function(segment, i) {
|
||||
if (i==0) {
|
||||
redir.hashPath += segment;
|
||||
} else {
|
||||
segmentMatch = segment.match(/(\w+)(.*)/);
|
||||
key = segmentMatch[1];
|
||||
redir.hashPath += pathParams[key] || location.hashSearch[key];
|
||||
redir.hashPath += segmentMatch[2] || '';
|
||||
delete redir.hashSearch[key];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// call custom redirectTo function
|
||||
redir = {hash: routeParams.redirectTo(pathParams, location.hash, location.hashPath,
|
||||
location.hashSearch)};
|
||||
}
|
||||
|
||||
location.update(redir);
|
||||
$updateView(); //TODO this is to work around the $location<=>$browser issues
|
||||
return;
|
||||
}
|
||||
|
||||
childScope = createScope(parentScope);
|
||||
$route.current = extend({}, routeParams, {
|
||||
scope: childScope,
|
||||
params: extend({}, location.hashSearch, pathParams)
|
||||
});
|
||||
}
|
||||
|
||||
//fire onChange callbacks
|
||||
forEach(onChange, parentScope.$tryEval);
|
||||
|
||||
if (childScope) {
|
||||
childScope.$become($route.current.controller);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.$watch(function(){return dirty + location.hash;}, updateRoute);
|
||||
|
||||
return $route;
|
||||
}, ['$location', '$updateView']);
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$updateView
|
||||
* @requires $browser
|
||||
*
|
||||
* @description
|
||||
* Calling `$updateView` enqueues the eventual update of the view. (Update the DOM to reflect the
|
||||
* model). The update is eventual, since there are often multiple updates to the model which may
|
||||
* be deferred. The default update delayed is 25 ms. This means that the view lags the model by
|
||||
* that time. (25ms is small enough that it is perceived as instantaneous by the user). The delay
|
||||
* can be adjusted by setting the delay property of the service.
|
||||
*
|
||||
* <pre>angular.service('$updateView').delay = 10</pre>
|
||||
*
|
||||
* The delay is there so that multiple updates to the model which occur sufficiently close
|
||||
* together can be merged into a single update.
|
||||
*
|
||||
* You don't usually call '$updateView' directly since angular does it for you in most cases,
|
||||
* but there are some cases when you need to call it.
|
||||
*
|
||||
* - `$updateView()` called automatically by angular:
|
||||
* - Your Application Controllers: Your controller code is called by angular and hence
|
||||
* angular is aware that you may have changed the model.
|
||||
* - Your Services: Your service is usually called by your controller code, hence same rules
|
||||
* apply.
|
||||
* - May need to call `$updateView()` manually:
|
||||
* - Widgets / Directives: If you listen to any DOM events or events on any third party
|
||||
* libraries, then angular is not aware that you may have changed state state of the
|
||||
* model, and hence you need to call '$updateView()' manually.
|
||||
* - 'setTimeout'/'XHR': If you call 'setTimeout' (instead of {@link angular.service.$defer})
|
||||
* or 'XHR' (instead of {@link angular.service.$xhr}) then you may be changing the model
|
||||
* without angular knowledge and you may need to call '$updateView()' directly.
|
||||
*
|
||||
* NOTE: if you wish to update the view immediately (without delay), you can do so by calling
|
||||
* {@link scope.$eval} at any time from your code:
|
||||
* <pre>scope.$root.$eval()</pre>
|
||||
*
|
||||
* In unit-test mode the update is instantaneous and synchronous to simplify writing tests.
|
||||
*
|
||||
*/
|
||||
|
||||
function serviceUpdateViewFactory($browser){
|
||||
var rootScope = this;
|
||||
var scheduled;
|
||||
function update(){
|
||||
scheduled = false;
|
||||
rootScope.$eval();
|
||||
}
|
||||
return $browser.isMock ? update : function(){
|
||||
if (!scheduled) {
|
||||
scheduled = true;
|
||||
$browser.defer(update, serviceUpdateViewFactory.delay);
|
||||
}
|
||||
};
|
||||
}
|
||||
serviceUpdateViewFactory.delay = 25;
|
||||
|
||||
angularServiceInject('$updateView', serviceUpdateViewFactory, ['$browser']);
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$window
|
||||
*
|
||||
* @description
|
||||
* A reference to the browser's `window` object. While `window`
|
||||
* is globally available in JavaScript, it causes testability problems, because
|
||||
* it is a global variable. In angular we always refer to it through the
|
||||
* `$window` service, so it may be overriden, removed or mocked for testing.
|
||||
*
|
||||
* All expressions are evaluated with respect to current scope so they don't
|
||||
* suffer from window globality.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
<input ng:init="$window = $service('$window'); greeting='Hello World!'" type="text" name="greeting" />
|
||||
<button ng:click="$window.alert(greeting)">ALERT</button>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
*/
|
||||
angularServiceInject("$window", bind(window, identity, window));
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$xhr.bulk
|
||||
* @requires $xhr
|
||||
* @requires $xhr.error
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
|
||||
var requests = [],
|
||||
scope = this;
|
||||
function bulkXHR(method, url, post, callback) {
|
||||
if (isFunction(post)) {
|
||||
callback = post;
|
||||
post = _null;
|
||||
}
|
||||
var currentQueue;
|
||||
forEach(bulkXHR.urls, function(queue){
|
||||
if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) {
|
||||
currentQueue = queue;
|
||||
}
|
||||
});
|
||||
if (currentQueue) {
|
||||
if (!currentQueue.requests) currentQueue.requests = [];
|
||||
currentQueue.requests.push({method: method, url: url, data:post, callback:callback});
|
||||
} else {
|
||||
$xhr(method, url, post, callback);
|
||||
}
|
||||
}
|
||||
bulkXHR.urls = {};
|
||||
bulkXHR.flush = function(callback){
|
||||
forEach(bulkXHR.urls, function(queue, url){
|
||||
var currentRequests = queue.requests;
|
||||
if (currentRequests && currentRequests.length) {
|
||||
queue.requests = [];
|
||||
queue.callbacks = [];
|
||||
$xhr('POST', url, {requests:currentRequests}, function(code, response){
|
||||
forEach(response, function(response, i){
|
||||
try {
|
||||
if (response.status == 200) {
|
||||
(currentRequests[i].callback || noop)(response.status, response.response);
|
||||
} else {
|
||||
$error(currentRequests[i], response);
|
||||
}
|
||||
} catch(e) {
|
||||
$log.error(e);
|
||||
}
|
||||
});
|
||||
(callback || noop)();
|
||||
});
|
||||
scope.$eval();
|
||||
}
|
||||
});
|
||||
};
|
||||
this.$onEval(PRIORITY_LAST, bulkXHR.flush);
|
||||
return bulkXHR;
|
||||
}, ['$xhr', '$xhr.error', '$log']);
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$xhr.cache
|
||||
* @function
|
||||
* @requires $xhr
|
||||
*
|
||||
* @description
|
||||
* Acts just like the {@link angular.service.$xhr $xhr} service but caches responses for `GET`
|
||||
* requests. All cache misses are delegated to the $xhr service.
|
||||
*
|
||||
* @property {function()} delegate Function to delegate all the cache misses to. Defaults to
|
||||
* the {@link angular.service.$xhr $xhr} service.
|
||||
* @property {object} data The hashmap where all cached entries are stored.
|
||||
*
|
||||
* @param {string} method HTTP method.
|
||||
* @param {string} url Destination URL.
|
||||
* @param {(string|Object)=} post Request body.
|
||||
* @param {function(number, (string|Object))} callback Response callback.
|
||||
* @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache
|
||||
* (if present) while a request is sent to the server for a fresh response that will update the
|
||||
* cached entry. The `callback` function will be called when the response is received.
|
||||
*/
|
||||
angularServiceInject('$xhr.cache', function($xhr, $defer, $log){
|
||||
var inflight = {}, self = this;
|
||||
function cache(method, url, post, callback, verifyCache){
|
||||
if (isFunction(post)) {
|
||||
callback = post;
|
||||
post = _null;
|
||||
}
|
||||
if (method == 'GET') {
|
||||
var data, dataCached;
|
||||
if (dataCached = cache.data[url]) {
|
||||
$defer(function() { callback(200, copy(dataCached.value)); });
|
||||
if (!verifyCache)
|
||||
return;
|
||||
}
|
||||
|
||||
if (data = inflight[url]) {
|
||||
data.callbacks.push(callback);
|
||||
} else {
|
||||
inflight[url] = {callbacks: [callback]};
|
||||
cache.delegate(method, url, post, function(status, response){
|
||||
if (status == 200)
|
||||
cache.data[url] = { value: response };
|
||||
var callbacks = inflight[url].callbacks;
|
||||
delete inflight[url];
|
||||
forEach(callbacks, function(callback){
|
||||
try {
|
||||
(callback||noop)(status, copy(response));
|
||||
} catch(e) {
|
||||
$log.error(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
cache.data = {};
|
||||
cache.delegate(method, url, post, callback);
|
||||
}
|
||||
}
|
||||
cache.data = {};
|
||||
cache.delegate = $xhr;
|
||||
return cache;
|
||||
}, ['$xhr.bulk', '$defer', '$log']);
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @workInProgress
|
||||
* @ngdoc service
|
||||
* @name angular.service.$xhr.error
|
||||
* @function
|
||||
* @requires $log
|
||||
*
|
||||
* @description
|
||||
* Error handler for {@link angular.service.$xhr $xhr service}. An application can replaces this
|
||||
* service with one specific for the application. The default implementation logs the error to
|
||||
* {@link angular.service.$log $log.error}.
|
||||
*
|
||||
* @param {Object} request Request object.
|
||||
*
|
||||
* The object has the following properties
|
||||
*
|
||||
* - `method` – `{string}` – The http request method.
|
||||
* - `url` – `{string}` – The request destination.
|
||||
* - `data` – `{(string|Object)=} – An optional request body.
|
||||
* - `callback` – `{function()}` – The callback function
|
||||
*
|
||||
* @param {Object} response Response object.
|
||||
*
|
||||
* The response object has the following properties:
|
||||
*
|
||||
* - status – {number} – Http status code.
|
||||
* - body – {string|Object} – Body of the response.
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
fetch a non-existent file and log an error in the console:
|
||||
<button ng:click="$service('$xhr')('GET', '/DOESNT_EXIST')">fetch</button>
|
||||
</doc:source>
|
||||
</doc:example>
|
||||
*/
|
||||
angularServiceInject('$xhr.error', function($log){
|
||||
return function(request, response){
|
||||
$log.error('ERROR: XHR: ' + request.url, request, response);
|
||||
};
|
||||
}, ['$log']);
|
||||