allow documentation to be in external file

* Load templates once instead of per request
* show timing information
* load files ending in .ngdoc and process them
This commit is contained in:
Misko Hevery 2010-11-22 12:05:01 -08:00
parent 2bbced212e
commit 58d0e8945d
11 changed files with 646 additions and 615 deletions

View file

@ -0,0 +1,53 @@
@workInProgress
@ngdoc overview
@name angular.directive
@namespace Namespace for all directives.
@description
A directive is an HTML attribute that you can use in an existing HTML element type or in a
DOM element type that you create as {@link angular.widget}, to modify that element's
properties. You can use any number of directives per element.
For example, you can add the ng:bind directive as an attribute of an HTML span element, as in
`<span ng:bind="1+2"></span>`. How does this work? The compiler passes the attribute value
`1+2` to the ng:bind extension, which in turn tells the {@link angular.scope} to watch that
expression and report changes. On any change it sets the span text to the expression value.
Here's how to define {@link angular.directive.ng:bind ng:bind}:
<pre>
angular.directive('ng:bind', function(expression, compiledElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value) {
linkElement.text(value);
});
};
});
</pre>
# Directive vs. Attribute Widget
Both [attribute widgets](#!angular.widget) and directives can compile a DOM element
attribute. So why have two different ways to do the same thing? The answer is that order
matters, but we have no control over the order in which attributes are read. To solve this
we apply attribute widget before the directive.
For example, consider this piece of HTML, which uses the directives `ng:repeat`, `ng:init`,
and `ng:bind`:
<pre>
<ul ng:init="people=['mike', 'mary']">
<li ng:repeat="person in people" ng:init="a=a+1" ng:bind="person"></li>
</ul>
</pre>
Notice that the order of execution matters here. We need to execute
{@link angular.directive.ng:repeat ng:repeat} before we run the
{@link angular.directive.ng:init ng:init} and `ng:bind` on the `<li/>;`. This is because we
want to run the `ng:init="a=a+1` and `ng:bind="person"` once for each person in people. We
could not have used directive to create this template because attributes are read in an
unspecified order and there is no way of guaranteeing that the repeater attribute would
execute first. Using the `ng:repeat` attribute directive ensures that we can transform the
DOM element into a template.
Widgets run before directives. Widgets may manipulate the DOM whereas directives are not
expected to do so, and so they run last.

View file

@ -0,0 +1,43 @@
@workInProgress
@ngdoc function
@name angular.element
@function
@description
Wraps a raw DOM element or HTML string as [jQuery](http://jquery.com) element.
`angular.element` is either an alias for [jQuery](http://api.jquery.com/jQuery/) function if
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.
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.
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:
- [addClass()](http://api.jquery.com/addClass/)
- [after()](http://api.jquery.com/after/)
- [append()](http://api.jquery.com/append/)
- [attr()](http://api.jquery.com/attr/)
- [bind()](http://api.jquery.com/bind/)
- [children()](http://api.jquery.com/children/)
- [clone()](http://api.jquery.com/clone/)
- [css()](http://api.jquery.com/css/)
- [data()](http://api.jquery.com/data/)
- [hasClass()](http://api.jquery.com/hasClass/)
- [parent()](http://api.jquery.com/parent/)
- [remove()](http://api.jquery.com/remove/)
- [removeAttr()](http://api.jquery.com/removeAttr/)
- [removeClass()](http://api.jquery.com/removeClass/)
- [removeData()](http://api.jquery.com/removeData/)
- [replaceWith()](http://api.jquery.com/replaceWith/)
- [text()](http://api.jquery.com/text/)
- [trigger()](http://api.jquery.com/trigger/)
@param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
@returns {Object} jQuery object.

76
docs/angular.filter.ngdoc Normal file
View file

@ -0,0 +1,76 @@
@workInProgress
@ngdoc overview
@name angular.filter
@namespace Namespace for all filters.
@description
# Overview
Filters are a standard way to format your data for display to the user. For example, you
might have the number 1234.5678 and would like to display it as US currency: $1,234.57.
Filters allow you to do just that. In addition to transforming the data, filters also modify
the DOM. This allows the filters to for example apply css styles to the filtered output if
certain conditions were met.
# Standard Filters
The Angular framework provides a standard set of filters for common operations, including:
{@link angular.filter.currency currency}, {@link angular.filter.json json},
{@link angular.filter.number number}, and {@link angular.filter.html html}. You can also add
your own filters.
# Syntax
Filters can be part of any {@link angular.scope} evaluation but are typically used with
{{bindings}}. Filters typically transform the data to a new data type, formating the data in
the process. Filters can be chained and take optional arguments. Here are few examples:
* No filter: {{1234.5678}} => 1234.5678
* Number filter: {{1234.5678|number}} => 1,234.57. Notice the “,” and rounding to two
significant digits.
* Filter with arguments: {{1234.5678|number:5}} => 1,234.56780. Filters can take optional
arguments, separated by colons in a binding. To number, the argument “5” requests 5 digits
to the right of the decimal point.
# Writing your own Filters
Writing your own filter is very easy: just define a JavaScript function on `angular.filter`.
The framework passes in the input value as the first argument to your function. Any filter
arguments are passed in as additional function arguments.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
the DOM in addition to transforming the input.
@exampleDescription
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
<script type="text/javascript">
angular.filter('reverse', function(input, uppercase, color) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
if (uppercase) {
out = out.toUpperCase();
}
if (color) {
this.$element.css('color', color);
}
return out;
});
</script>
<input name="text" type="text" value="hello" /><br>
No filter: {{text}}<br>
Reverse: {{text|reverse}}<br>
Reverse + uppercase: {{text|reverse:true}}<br>
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}

View file

@ -0,0 +1,78 @@
@workInProgress
@ngdoc overview
@name angular.formatter
@namespace Namespace for all formats.
@description
# Overview
The formatters are responsible for translating user readable text in an input widget to a
data model stored in an application.
# Writting your own Formatter
Writing your own formatter is easy. Just register a pair of JavaScript functions with
`angular.formatter`. One function for parsing user input text to the stored form,
and one for formatting the stored data to user-visible text.
Here is an example of a "reverse" formatter: The data is stored in uppercase and in
reverse, while it is displayed in lower case and non-reversed. User edits are
automatically parsed into the internal form and data changes are automatically
formatted to the viewed form.
<pre>
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</pre>
@example
<script type="text/javascript">
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</script>
Formatted:
<input type="text" name="data" value="angular" ng:format="reverse"/>
<br/>
Stored:
<input type="text" name="data"/><br/>
<pre>{{data}}</pre>
@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');
});

4
docs/angular.ngdoc Normal file
View file

@ -0,0 +1,4 @@
@workInProgress
@ngdoc overview
@name angular
@namespace The exported angular namespace.

159
docs/angular.service.ngdoc Normal file
View file

@ -0,0 +1,159 @@
@workInProgress
@ngdoc overview
@name angular.service
@description
# Overview
Services are substituable objects, which are wired together using dependency injection.
Each service could have dependencies (other services), which are passed in constructor.
Because JS is dynamicaly typed language, dependency injection can not use static types
to satisfy these dependencies, so each service must explicitely define its dependencies.
This is done by `$inject` property.
For now, life time of all services is the same as the life time of page.
# Built-in services
The Angular framework provides a standard set of services for common operations.
You can write your own services and rewrite these standard services as well.
Like other core angular variables, the built-in services always start with $.
* `angular.service.$browser`
* `angular.service.$window`
* `angular.service.$document`
* `angular.service.$location`
* `angular.service.$log`
* `angular.service.$exceptionHandler`
* `angular.service.$hover`
* `angular.service.$invalidWidgets`
* `angular.service.$route`
* `angular.service.$xhr`
* `angular.service.$xhr.error`
* `angular.service.$xhr.bulk`
* `angular.service.$xhr.cache`
* `angular.service.$resource`
* `angular.service.$cookies`
* `angular.service.$cookieStore`
# Writing your own custom services
Angular provides only set of basic services, so you will probably need to write your custom
service very soon. To do so, you need to write a factory function and register this function
to angular's dependency injector. This factory function must return an object - your service
(it is not called with new operator).
**angular.service** has three parameters:
- `{string} name` - Name of the service
- `{function()} factory` - Factory function (called just once by DI)
- `{Object} config` - Hash of configuration (`$inject`, `$creation`)
If your service requires - depends on other services, you need to specify them
in config hash - property $inject. This property is an array of strings (service names).
These dependencies will be passed as parameters to the factory function by DI.
This approach is very useful when testing, as you can inject mocks/stubs/dummies.
Here is an example of very simple service. This service requires $window service (it's
passed as a parameter to factory function) and it's just a function.
This service simple stores all notifications and after third one, it displays all of them by
window alert.
<pre>
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']});
</pre>
And here is a unit test for this service. We use Jasmine spy (mock) instead of real browser's alert.
<pre>
var mock, notify;
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
notify = angular.service('notify')(mock);
});
it('should not alert first two notifications', function() {
notify('one');
notify('two');
expect(mock.alert).not.toHaveBeenCalled();
});
it('should alert all after third notification', function() {
notify('one');
notify('two');
notify('three');
expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
});
it('should clear messages after alert', function() {
notify('one');
notify('two');
notify('third');
notify('more');
notify('two');
notify('third');
expect(mock.alert.callCount).toEqual(2);
expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
});
</pre>
# Injecting services into controllers
Using services in a controllers is very similar to using service in other service.
Again, we will use dependency injection.
JavaScript is dynamic language, so DI is not able to figure out which services to inject by
static types (like in static typed languages). Therefore you must specify the service name
by the `$inject` property - it's an array that contains strings with names of services to be
injected. The name must match the id that service has been registered as with angular.
The order of the services in the array matters, because this order will be used when calling
the factory function with injected parameters. The names of parameters in factory function
don't matter, but by convention they match the service ids.
<pre>
function myController($loc, $log) {
this.firstMethod = function() {
// use $location service
$loc.setHash();
};
this.secondMethod = function() {
// use $log service
$log.info('...');
};
}
// which services to inject ?
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']});
function myController(notifyService) {
this.callNotify = function(msg) {
notifyService(msg);
};
}
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>

View file

@ -0,0 +1,73 @@
@workInProgress
@ngdoc overview
@name angular.validator
@namespace Namespace for all filters.
@description
# Overview
Validators are a standard way to check the user input against a specific criteria. For
example, you might need to check that an input field contains a well-formed phone number.
# Syntax
Attach a validator on user input widgets using the `ng:validate` attribute.
<doc:example>
<doc:source>
Change me: &lt;input type="text" name="number" ng:validate="integer" value="123"&gt;
</doc:source>
<doc:scenario>
it('should validate the default number string', function() {
expect(element('input[name=number]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate "foo"', function() {
input('number').enter('foo');
expect(element('input[name=number]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
# Writing your own Validators
Writing your own validator is easy. To make a function available as a
validator, just define the JavaScript function on the `angular.validator`
object. <angular/> passes in the input to validate as the first argument
to your function. Any additional validator arguments are passed in as
additional arguments to your function.
You can use these variables in the function:
* `this` — The current scope.
* `this.$element` — The DOM element containing the binding. This allows the filter to manipulate
the DOM in addition to transforming the input.
In this example we have written a upsTrackingNo validator.
It marks the input text "valid" only when the user enters a well-formed
UPS tracking number.
@css ng-validation-error
When validation fails, this css class is applied to the binding, making its borders red by
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"/>
@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/);
});

73
docs/angular.widget.ngdoc Normal file
View file

@ -0,0 +1,73 @@
@workInProgress
@ngdoc overview
@name angular.widget
@namespace Namespace for all widgets.
@description
# Overview
Widgets allow you to create DOM elements that the browser doesn't
already understand. You create the widget in your namespace and
assign it behavior. You can only bind one widget per DOM element
(unlike directives, in which you can use any number per DOM
element). Widgets are expected to manipulate the DOM tree by
adding new elements whereas directives are expected to only modify
element properties.
Widgets come in two flavors: element and attribute.
# Element Widget
Let's say we would like to create a new element type in the
namespace `my` that can watch an expression and alert() the user
with each new value.
<pre>
&lt;my:watch exp="name"/&gt;
</pre>
You can implement `my:watch` like this:
<pre>
angular.widget('my:watch', function(compileElement) {
var compiler = this;
var exp = compileElement.attr('exp');
return function(linkElement) {
var currentScope = this;
currentScope.$watch(exp, function(value){
alert(value);
}};
};
});
</pre>
# Attribute Widget
Let's implement the same widget, but this time as an attribute
that can be added to any existing DOM element.
<pre>
&lt;div my-watch="name"&gt;text&lt;/div&gt;
</pre>
You can implement `my:watch` attribute like this:
<pre>
angular.widget('@my:watch', function(expression, compileElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value){
alert(value);
});
};
});
</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>

View file

@ -16,39 +16,29 @@ var keywordPages = [];
var SRC_DIR = "docs/";
var OUTPUT_DIR = "build/docs/";
var NEW_LINE = /\n\r?/;
var TEMPLATES = {};
var start = now();
function now(){ return new Date().getTime(); }
var work = callback.chain(function () {
console.log('Parsing Angular Reference Documentation');
mkdirPath(OUTPUT_DIR, work.waitFor(function(){
findJsFiles('src', work.waitMany(function(file) {
//console.log('reading', file, '...');
findNgDoc(file, work.waitMany(function(doc) {
parseNgDoc(doc);
processNgDoc(documentation, doc);
}));
findJsFiles('src', work.waitMany(function(file) {
//console.log('reading', file, '...');
findNgDocInJsFile(file, work.waitMany(function(doc) {
parseNgDoc(doc);
processNgDoc(documentation, doc);
}));
}));
findNgDocInDir(SRC_DIR, work.waitMany(function(doc){
parseNgDoc(doc);
processNgDoc(documentation, doc);
}));
loadTemplates(TEMPLATES, work.waitFor());
mkdirPath(OUTPUT_DIR, work.waitFor());
}).onError(function(err){
console.log('ERROR:', err.stack || err);
}).onDone(function(){
keywordPages.sort(function(a,b){
// supper ugly comparator that orders all utility methods and objects before all the other stuff
// like widgets, directives, services, etc.
// Mother of all beatiful code please forgive me for the sin that this code certainly is.
if (a.name === b.name) return 0;
if (a.name === 'angular') return -1;
if (b.name === 'angular') return 1;
function namespacedName(page) {
return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
}
var namespacedA = namespacedName(a),
namespacedB = namespacedName(b);
return namespacedA < namespacedB ? -1 : 1;
});
keywordPages.sort(keywordSort);
writeDoc(documentation.pages);
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(keywordPages)}, callback.chain());
mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain());
@ -58,7 +48,7 @@ var work = callback.chain(function () {
mergeTemplate('docs.js', 'docs.js', documentation, callback.chain());
mergeTemplate('doc_widgets.css', 'doc_widgets.css', documentation, callback.chain());
mergeTemplate('doc_widgets.js', 'doc_widgets.js', documentation, callback.chain());
console.log('DONE');
console.log('DONE', now() - start, 'ms.');
});
if (!this.testmode) work();
////////////////////
@ -163,7 +153,7 @@ function markdownTag(doc, name, value) {
replace(/\<\/pre\>/gmi, '</pre></div>');
}
R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m
var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m;
// 1 123 3 4 42
function markdown(text) {
@ -313,7 +303,7 @@ function parseNgDoc(doc){
}
}
function findNgDoc(file, callback) {
function findNgDocInJsFile(file, callback) {
fs.readFile(file, callback.waitFor(function(err, content){
var lines = content.toString().split(NEW_LINE);
var doc;
@ -346,6 +336,22 @@ function findNgDoc(file, callback) {
}));
}
function loadTemplates(cache, callback){
fs.readdir('docs', callback.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
var match = file.match(/^(.*)\.template$/);
if (match) {
fs.readFile(SRC_DIR + file, callback.waitFor(function(err, content){
if (err) return this.error(err);
cache[match[1]] = content.toString();
}));
}
});
callback();
}));
};
function findJsFiles(dir, callback){
fs.readdir(dir, callback.waitFor(function(err, files){
if (err) return this.error(err);
@ -365,7 +371,7 @@ function findJsFiles(dir, callback){
function processNgDoc(documentation, doc) {
if (!doc.ngdoc) return;
console.log('Found:', doc.ngdoc + ':' + doc.name);
//console.log('Found:', doc.ngdoc + ':' + doc.name);
documentation.byName[doc.name] = doc;
@ -385,10 +391,50 @@ function processNgDoc(documentation, doc) {
}
}
function writeDoc(pages) {
function writeDoc(pages, callback) {
pages.forEach(function(doc) {
mergeTemplate(
doc.ngdoc + '.template',
doc.name + '.html', doc, callback.chain());
var template = TEMPLATES[doc.ngdoc];
if (!template) throw new Error("No template for:" + doc.ngdoc);
var content = mustache.to_html(template, doc);
fs.writeFile(OUTPUT_DIR + doc.name + '.html', content, callback);
});
}
function findNgDocInDir(directory, docNotify) {
fs.readdir(directory, docNotify.waitFor(function(err, files){
if (err) return this.error(err);
files.forEach(function(file){
console.log(file);
if (!file.match(/\.ngdoc$/)) return;
fs.readFile(directory + file, docNotify.waitFor(function(err, content){
if (err) return this.error(err);
docNotify({
raw:{
text:content.toString(),
file: directory + file,
line: 1}
});
}));
});
docNotify.done();
}));
}
function keywordSort(a,b){
// supper ugly comparator that orders all utility methods and objects before all the other stuff
// like widgets, directives, services, etc.
// Mother of all beautiful code please forgive me for the sin that this code certainly is.
if (a.name === b.name) return 0;
if (a.name === 'angular') return -1;
if (b.name === 'angular') return 1;
function namespacedName(page) {
return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
}
var namespacedA = namespacedName(a),
namespacedB = namespacedName(b);
return namespacedA < namespacedB ? -1 : 1;
}

View file

@ -22,7 +22,7 @@
<script src="doc_widgets.js"></script>
<script src="docs-data.js"></script>
</head>
<body>
<body style="display:none;" ng:show="true">
<div id="header">
<h1>
<span class="main-title">{{getTitle()}}</span>

View file

@ -87,599 +87,25 @@ var _undefined = undefined,
_ = window['_'],
/** holds major version number for IE or NaN for real browsers */
msie = parseInt((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1], 10),
/**
* @workInProgress
* @ngdoc function
* @name angular.element
* @function
*
* @description
* Wraps a raw DOM element or HTML string as [jQuery](http://jquery.com) element.
* `angular.element` is either an alias for [jQuery](http://api.jquery.com/jQuery/) function if
* 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.
*
* 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.
*
* 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:
*
* - [addClass()](http://api.jquery.com/addClass/)
* - [after()](http://api.jquery.com/after/)
* - [append()](http://api.jquery.com/append/)
* - [attr()](http://api.jquery.com/attr/)
* - [bind()](http://api.jquery.com/bind/)
* - [children()](http://api.jquery.com/children/)
* - [clone()](http://api.jquery.com/clone/)
* - [css()](http://api.jquery.com/css/)
* - [data()](http://api.jquery.com/data/)
* - [hasClass()](http://api.jquery.com/hasClass/)
* - [parent()](http://api.jquery.com/parent/)
* - [remove()](http://api.jquery.com/remove/)
* - [removeAttr()](http://api.jquery.com/removeAttr/)
* - [removeClass()](http://api.jquery.com/removeClass/)
* - [removeData()](http://api.jquery.com/removeData/)
* - [replaceWith()](http://api.jquery.com/replaceWith/)
* - [text()](http://api.jquery.com/text/)
* - [trigger()](http://api.jquery.com/trigger/)
*
* @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
* @returns {Object} jQuery object.
*/
jqLite = jQuery || jqLiteWrap,
slice = Array.prototype.slice,
push = Array.prototype.push,
error = window[$console] ? bind(window[$console], window[$console]['error'] || noop) : noop,
/**
* @workInProgress
* @ngdoc overview
* @name angular
* @namespace The exported angular namespace.
*/
angular = window[$angular] || (window[$angular] = {}),
angularTextMarkup = extensionMap(angular, 'markup'),
angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.directive
* @namespace Namespace for all directives.
*
* @description
* A directive is an HTML attribute that you can use in an existing HTML element type or in a
* DOM element type that you create as {@link angular.widget}, to modify that element's
* properties. You can use any number of directives per element.
*
* For example, you can add the ng:bind directive as an attribute of an HTML span element, as in
* `<span ng:bind="1+2"></span>`. How does this work? The compiler passes the attribute value
* `1+2` to the ng:bind extension, which in turn tells the {@link angular.scope} to watch that
* expression and report changes. On any change it sets the span text to the expression value.
*
* Here's how to define {@link angular.directive.ng:bind ng:bind}:
* <pre>
angular.directive('ng:bind', function(expression, compiledElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value) {
linkElement.text(value);
});
};
});
* </pre>
*
* # Directive vs. Attribute Widget
* Both [attribute widgets](#!angular.widget) and directives can compile a DOM element
* attribute. So why have two different ways to do the same thing? The answer is that order
* matters, but we have no control over the order in which attributes are read. To solve this
* we apply attribute widget before the directive.
*
* For example, consider this piece of HTML, which uses the directives `ng:repeat`, `ng:init`,
* and `ng:bind`:
* <pre>
<ul ng:init="people=['mike', 'mary']">
<li ng:repeat="person in people" ng:init="a=a+1" ng:bind="person"></li>
</ul>
* </pre>
*
* Notice that the order of execution matters here. We need to execute
* {@link angular.directive.ng:repeat ng:repeat} before we run the
* {@link angular.directive.ng:init ng:init} and `ng:bind` on the `<li/>;`. This is because we
* want to run the `ng:init="a=a+1` and `ng:bind="person"` once for each person in people. We
* could not have used directive to create this template because attributes are read in an
* unspecified order and there is no way of guaranteeing that the repeater attribute would
* execute first. Using the `ng:repeat` attribute directive ensures that we can transform the
* DOM element into a template.
*
* Widgets run before directives. Widgets may manipulate the DOM whereas directives are not
* expected to do so, and so they run last.
*/
/** @name angular.directive */
angularDirective = extensionMap(angular, 'directive'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.widget
* @namespace Namespace for all widgets.
* @description
* # Overview
* Widgets allow you to create DOM elements that the browser doesn't
* already understand. You create the widget in your namespace and
* assign it behavior. You can only bind one widget per DOM element
* (unlike directives, in which you can use any number per DOM
* element). Widgets are expected to manipulate the DOM tree by
* adding new elements whereas directives are expected to only modify
* element properties.
*
* Widgets come in two flavors: element and attribute.
*
* # Element Widget
* Let's say we would like to create a new element type in the
* namespace `my` that can watch an expression and alert() the user
* with each new value.
*
* <pre>
* &lt;my:watch exp="name"/&gt;
* </pre>
*
* You can implement `my:watch` like this:
* <pre>
* angular.widget('my:watch', function(compileElement) {
* var compiler = this;
* var exp = compileElement.attr('exp');
* return function(linkElement) {
* var currentScope = this;
* currentScope.$watch(exp, function(value){
* alert(value);
* }};
* };
* });
* </pre>
*
* # Attribute Widget
* Let's implement the same widget, but this time as an attribute
* that can be added to any existing DOM element.
* <pre>
* &lt;div my-watch="name"&gt;text&lt;/div&gt;
* </pre>
* You can implement `my:watch` attribute like this:
* <pre>
* angular.widget('@my:watch', function(expression, compileElement) {
* var compiler = this;
* return function(linkElement) {
* var currentScope = this;
* currentScope.$watch(expression, function(value){
* alert(value);
* });
* };
* });
* </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>
*/
/** @name angular.widget */
angularWidget = extensionMap(angular, 'widget', lowercase),
/**
* @workInProgress
* @ngdoc overview
* @name angular.validator
* @namespace Namespace for all filters.
* @description
* # Overview
* Validators are a standard way to check the user input against a specific criteria. For
* example, you might need to check that an input field contains a well-formed phone number.
*
* # Syntax
* Attach a validator on user input widgets using the `ng:validate` attribute.
*
* <doc:example>
* <doc:source>
* Change me: &lt;input type="text" name="number" ng:validate="integer" value="123"&gt;
* </doc:source>
* <doc:scenario>
* it('should validate the default number string', function() {
* expect(element('input[name=number]').attr('class')).
* not().toMatch(/ng-validation-error/);
* });
* it('should not validate "foo"', function() {
* input('number').enter('foo');
* expect(element('input[name=number]').attr('class')).
* toMatch(/ng-validation-error/);
* });
* </doc:scenario>
* </doc:example>
*
*
* # Writing your own Validators
* Writing your own validator is easy. To make a function available as a
* validator, just define the JavaScript function on the `angular.validator`
* object. <angular/> passes in the input to validate as the first argument
* to your function. Any additional validator arguments are passed in as
* additional arguments to your function.
*
* You can use these variables in the function:
*
* * `this` The current scope.
* * `this.$element` The DOM element containing the binding. This allows the filter to manipulate
* the DOM in addition to transforming the input.
*
* In this example we have written a upsTrackingNo validator.
* It marks the input text "valid" only when the user enters a well-formed
* UPS tracking number.
*
* @css ng-validation-error
* When validation fails, this css class is applied to the binding, making its borders red by
* 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"/>
*
* @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/);
* });
*
*/
/** @name angular.validator */
angularValidator = extensionMap(angular, 'validator'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.filter
* @namespace Namespace for all filters.
* @description
* # Overview
* Filters are a standard way to format your data for display to the user. For example, you
* might have the number 1234.5678 and would like to display it as US currency: $1,234.57.
* Filters allow you to do just that. In addition to transforming the data, filters also modify
* the DOM. This allows the filters to for example apply css styles to the filtered output if
* certain conditions were met.
*
*
* # Standard Filters
*
* The Angular framework provides a standard set of filters for common operations, including:
* {@link angular.filter.currency currency}, {@link angular.filter.json json},
* {@link angular.filter.number number}, and {@link angular.filter.html html}. You can also add
* your own filters.
*
*
* # Syntax
*
* Filters can be part of any {@link angular.scope} evaluation but are typically used with
* {{bindings}}. Filters typically transform the data to a new data type, formating the data in
* the process. Filters can be chained and take optional arguments. Here are few examples:
*
* * No filter: {{1234.5678}} => 1234.5678
* * Number filter: {{1234.5678|number}} => 1,234.57. Notice the , and rounding to two
* significant digits.
* * Filter with arguments: {{1234.5678|number:5}} => 1,234.56780. Filters can take optional
* arguments, separated by colons in a binding. To number, the argument 5 requests 5 digits
* to the right of the decimal point.
*
*
* # Writing your own Filters
*
* Writing your own filter is very easy: just define a JavaScript function on `angular.filter`.
* The framework passes in the input value as the first argument to your function. Any filter
* arguments are passed in as additional function arguments.
*
* You can use these variables in the function:
*
* * `this` The current scope.
* * `this.$element` The DOM element containing the binding. This allows the filter to manipulate
* the DOM in addition to transforming the input.
*
*
* @exampleDescription
* 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
<script type="text/javascript">
angular.filter('reverse', function(input, uppercase, color) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
if (uppercase) {
out = out.toUpperCase();
}
if (color) {
this.$element.css('color', color);
}
return out;
});
</script>
<input name="text" type="text" value="hello" /><br>
No filter: {{text}}<br>
Reverse: {{text|reverse}}<br>
Reverse + uppercase: {{text|reverse:true}}<br>
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
*/
/** @name angular.fileter */
angularFilter = extensionMap(angular, 'filter'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.formatter
* @namespace Namespace for all formats.
* @description
* # Overview
* The formatters are responsible for translating user readable text in an input widget to a
* data model stored in an application.
*
* # Writting your own Formatter
* Writing your own formatter is easy. Just register a pair of JavaScript functions with
* `angular.formatter`. One function for parsing user input text to the stored form,
* and one for formatting the stored data to user-visible text.
*
* Here is an example of a "reverse" formatter: The data is stored in uppercase and in
* reverse, while it is displayed in lower case and non-reversed. User edits are
* automatically parsed into the internal form and data changes are automatically
* formatted to the viewed form.
*
* <pre>
* function reverse(text) {
* var reversed = [];
* for (var i = 0; i < text.length; i++) {
* reversed.unshift(text.charAt(i));
* }
* return reversed.join('');
* }
*
* angular.formatter('reverse', {
* parse: function(value){
* return reverse(value||'').toUpperCase();
* },
* format: function(value){
* return reverse(value||'').toLowerCase();
* }
* });
* </pre>
*
* @example
* <script type="text/javascript">
* function reverse(text) {
* var reversed = [];
* for (var i = 0; i < text.length; i++) {
* reversed.unshift(text.charAt(i));
* }
* return reversed.join('');
* }
*
* angular.formatter('reverse', {
* parse: function(value){
* return reverse(value||'').toUpperCase();
* },
* format: function(value){
* return reverse(value||'').toLowerCase();
* }
* });
* </script>
*
* Formatted:
* <input type="text" name="data" value="angular" ng:format="reverse"/>
* <br/>
*
* Stored:
* <input type="text" name="data"/><br/>
* <pre>{{data}}</pre>
*
*
* @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');
* });
*/
/** @name angular.formatter */
angularFormatter = extensionMap(angular, 'formatter'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.service
*
* @description
* # Overview
* Services are substituable objects, which are wired together using dependency injection.
* Each service could have dependencies (other services), which are passed in constructor.
* Because JS is dynamicaly typed language, dependency injection can not use static types
* to satisfy these dependencies, so each service must explicitely define its dependencies.
* This is done by `$inject` property.
*
* For now, life time of all services is the same as the life time of page.
*
*
* # Built-in services
* The Angular framework provides a standard set of services for common operations.
* You can write your own services and rewrite these standard services as well.
* Like other core angular variables, the built-in services always start with $.
*
* * `angular.service.$browser`
* * `angular.service.$window`
* * `angular.service.$document`
* * `angular.service.$location`
* * `angular.service.$log`
* * `angular.service.$exceptionHandler`
* * `angular.service.$hover`
* * `angular.service.$invalidWidgets`
* * `angular.service.$route`
* * `angular.service.$xhr`
* * `angular.service.$xhr.error`
* * `angular.service.$xhr.bulk`
* * `angular.service.$xhr.cache`
* * `angular.service.$resource`
* * `angular.service.$cookies`
* * `angular.service.$cookieStore`
*
* # Writing your own custom services
* Angular provides only set of basic services, so you will probably need to write your custom
* service very soon. To do so, you need to write a factory function and register this function
* to angular's dependency injector. This factory function must return an object - your service
* (it is not called with new operator).
*
* **angular.service** has three parameters:
*
* - `{string} name` - Name of the service
* - `{function()} factory` - Factory function (called just once by DI)
* - `{Object} config` - Hash of configuration (`$inject`, `$creation`)
*
* If your service requires - depends on other services, you need to specify them
* in config hash - property $inject. This property is an array of strings (service names).
* These dependencies will be passed as parameters to the factory function by DI.
* This approach is very useful when testing, as you can inject mocks/stubs/dummies.
*
* Here is an example of very simple service. This service requires $window service (it's
* passed as a parameter to factory function) and it's just a function.
*
* This service simple stores all notifications and after third one, it displays all of them by
* window alert.
* <pre>
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']});
* </pre>
*
* And here is a unit test for this service. We use Jasmine spy (mock) instead of real browser's alert.
* <pre>
* var mock, notify;
*
* beforeEach(function() {
* mock = {alert: jasmine.createSpy()};
* notify = angular.service('notify')(mock);
* });
*
* it('should not alert first two notifications', function() {
* notify('one');
* notify('two');
* expect(mock.alert).not.toHaveBeenCalled();
* });
*
* it('should alert all after third notification', function() {
* notify('one');
* notify('two');
* notify('three');
* expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
* });
*
* it('should clear messages after alert', function() {
* notify('one');
* notify('two');
* notify('third');
* notify('more');
* notify('two');
* notify('third');
* expect(mock.alert.callCount).toEqual(2);
* expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
* });
* </pre>
*
* # Injecting services into controllers
* Using services in a controllers is very similar to using service in other service.
* Again, we will use dependency injection.
*
* JavaScript is dynamic language, so DI is not able to figure out which services to inject by
* static types (like in static typed languages). Therefore you must specify the service name
* by the `$inject` property - it's an array that contains strings with names of services to be
* injected. The name must match the id that service has been registered as with angular.
* The order of the services in the array matters, because this order will be used when calling
* the factory function with injected parameters. The names of parameters in factory function
* don't matter, but by convention they match the service ids.
* <pre>
* function myController($loc, $log) {
* this.firstMethod = function() {
* // use $location service
* $loc.setHash();
* };
* this.secondMethod = function() {
* // use $log service
* $log.info('...');
* };
* }
* // which services to inject ?
* 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']});
*
* function myController(notifyService) {
* this.callNotify = function(msg) {
* notifyService(msg);
* };
* }
*
* 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>
*/
/** @name angular.service */
angularService = extensionMap(angular, 'service'),
angularCallbacks = extensionMap(angular, 'callbacks'),
nodeName,
@ -942,7 +368,7 @@ function isNumber(value){ return typeof value == $number;}
* @function
*
* @description
* Checks if a reference is defined.
* Checks if value is a date.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Date`.