changed the documentation @example to use <doc:example>

This commit is contained in:
Misko Hevery 2011-01-31 16:21:29 -08:00
parent ed768ebc53
commit ba6b68b6ae
20 changed files with 1841 additions and 1653 deletions

View file

@ -46,12 +46,13 @@ You can use these variables in the function:
the DOM in addition to transforming the input. the DOM in addition to transforming the input.
@exampleDescription @example
The following example filter reverses a text string. In addition, it conditionally makes the 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 text upper-case (to demonstrate optional arguments) and assigns color (to demonstrate DOM
modification). modification).
@example <doc:example>
<doc:source>
<script type="text/javascript"> <script type="text/javascript">
angular.filter('reverse', function(input, uppercase, color) { angular.filter('reverse', function(input, uppercase, color) {
var out = ""; var out = "";
@ -73,4 +74,14 @@ You can use these variables in the function:
Reverse: {{text|reverse}}<br> Reverse: {{text|reverse}}<br>
Reverse + uppercase: {{text|reverse:true}}<br> Reverse + uppercase: {{text|reverse:true}}<br>
Reverse + uppercase + blue: {{text|reverse:true:"blue"}} 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>

View file

@ -37,42 +37,46 @@ angular.formatter('reverse', {
</pre> </pre>
@example @example
<script type="text/javascript"> <doc:example>
function reverse(text) { <doc:source>
var reversed = []; <script type="text/javascript">
for (var i = 0; i < text.length; i++) { function reverse(text) {
reversed.unshift(text.charAt(i)); var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
} }
return reversed.join('');
}
angular.formatter('reverse', { angular.formatter('reverse', {
parse: function(value){ parse: function(value){
return reverse(value||'').toUpperCase(); return reverse(value||'').toUpperCase();
}, },
format: function(value){ format: function(value){
return reverse(value||'').toLowerCase(); return reverse(value||'').toLowerCase();
} }
}); });
</script> </script>
Formatted: Formatted:
<input type="text" name="data" value="angular" ng:format="reverse"/> <input type="text" name="data" value="angular" ng:format="reverse"/>
<br/> <br/>
Stored: Stored:
<input type="text" name="data"/><br/> <input type="text" name="data"/><br/>
<pre>{{data}}</pre> <pre>{{data}}</pre>
</doc:source>
<doc: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');
});
</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');
});

View file

@ -17,22 +17,22 @@ services if needed.
Like other core angular variables and identifiers, the built-in services always start with `$`. Like other core angular variables and identifiers, the built-in services always start with `$`.
* `{@link angular.service.$browser $browser}` * {@link angular.service.$browser $browser}
* `{@link angular.service.$window $window}` * {@link angular.service.$window $window}
* `{@link angular.service.$document $document}` * {@link angular.service.$document $document}
* `{@link angular.service.$location $location}` * {@link angular.service.$location $location}
* `{@link angular.service.$log $log}` * {@link angular.service.$log $log}
* `{@link angular.service.$exceptionHandler $exceptionHandler}` * {@link angular.service.$exceptionHandler $exceptionHandler}
* `{@link angular.service.$hover $hover}` * {@link angular.service.$hover $hover}
* `{@link angular.service.$invalidWidgets $invalidWidgets}` * {@link angular.service.$invalidWidgets $invalidWidgets}
* `{@link angular.service.$route $route}` * {@link angular.service.$route $route}
* `{@link angular.service.$xhr $xhr}` * {@link angular.service.$xhr $xhr}
* `{@link angular.service.$xhr.error $xhr.error}` * {@link angular.service.$xhr.error $xhr.error}
* `{@link angular.service.$xhr.bulk $xhr.bulk}` * {@link angular.service.$xhr.bulk $xhr.bulk}
* `{@link angular.service.$xhr.cache $xhr.cache}` * {@link angular.service.$xhr.cache $xhr.cache}
* `{@link angular.service.$resource $resource}` * {@link angular.service.$resource $resource}
* `{@link angular.service.$cookies $cookies}` * {@link angular.service.$cookies $cookies}
* `{@link angular.service.$cookieStore $cookieStore}` * {@link angular.service.$cookieStore $cookieStore}
# Writing your own custom services # Writing your own custom services
angular provides only set of basic services, so for any nontrivial application it will be necessary 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> </pre>
@example @example
<script type="text/javascript"> <doc:example>
angular.service('notify', function(win) { <doc:source>
var msgs = []; <script type="text/javascript">
return function(msg) { angular.service('notify', function(win) {
msgs.push(msg); var msgs = [];
if (msgs.length == 3) { return function(msg) {
win.alert(msgs.join("\n")); msgs.push(msg);
msgs = []; if (msgs.length == 3) {
} win.alert(msgs.join("\n"));
}; msgs = [];
}, {$inject: ['$window']}); }
};
}, {$inject: ['$window']});
function myController(notifyService) { function myController(notifyService) {
this.callNotify = function(msg) { this.callNotify = function(msg) {
notifyService(msg); notifyService(msg);
}; };
} }
myController.$inject = ['notify']; myController.$inject = ['notify'];
</script> </script>
<div ng:controller="myController"> <div ng:controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p> <p>Let's try this simple notify service, injected into the controller...</p>
<input ng:init="message='test'" type="text" name="message" /> <input ng:init="message='test'" type="text" name="message" />
<button ng:click="callNotify(message);">NOTIFY</button> <button ng:click="callNotify(message);">NOTIFY</button>
</div> </div>
</doc:source>
<doc:scenario>
it('should test service', function(){
expect(element(':input[name=message]').val()).toEqual('test');
});
</doc:scenario>
</doc:example>

View file

@ -50,24 +50,28 @@ UPS tracking number.
default. default.
@example @example
<script> <doc:example>
angular.validator('upsTrackingNo', function(input, format) { <doc:source>
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$"); <script>
return input.match(regexp)?"":"The format must match " + format; angular.validator('upsTrackingNo', function(input, format) {
}); var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
</script> return input.match(regexp)?"":"The format must match " + format;
<input type="text" name="trackNo" size="40" });
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'" </script>
value="1Z 123 456 78 9012 345 6"/> <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 not validate in correct UPS tracking number', function() {
it('should validate correct UPS tracking number', function() { input('trackNo').enter('foo');
expect(element('input[name=trackNo]').attr('class')). expect(element('input[name=trackNo]').attr('class')).
not().toMatch(/ng-validation-error/); toMatch(/ng-validation-error/);
}); });
</doc:scenario>
it('should not validate in correct UPS tracking number', function() { </doc:example>
input('trackNo').enter('foo');
expect(element('input[name=trackNo]').attr('class')).
toMatch(/ng-validation-error/);
});

View file

@ -57,17 +57,22 @@ angular.widget('@my:watch', function(expression, compileElement) {
</pre> </pre>
@example @example
<script> <doc:example>
angular.widget('my:time', function(compileElement){ <doc:source>
compileElement.css('display', 'block'); <script>
return function(linkElement){ angular.widget('my:time', function(compileElement){
function update(){ compileElement.css('display', 'block');
linkElement.text('Current time is: ' + new Date()); return function(linkElement){
setTimeout(update, 1000); function update(){
} linkElement.text('Current time is: ' + new Date());
update(); setTimeout(update, 1000);
}; }
}); update();
</script> };
<my:time></my:time> });
</script>
<my:time></my:time>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>

View file

@ -7,34 +7,6 @@ describe('dom', function(){
dom = new DOM(); 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(){ describe('h', function(){
it('should render using function', function(){ it('should render using function', function(){

View file

@ -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(){ it('should parse filename', function(){
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1); var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
doc.parse(0); doc.parse(0);
@ -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(){ describe('markdown', function(){
var markdown = ngdoc.markdown;
it('should replace angular in markdown', function(){ it('should replace angular in markdown', function(){
expect(markdown('<angular/>')). expect(new Doc().markdown('<angular/>')).
toEqual('<p><tt>&lt;angular/&gt;</tt></p>'); toEqual('<p><tt>&lt;angular/&gt;</tt></p>');
}); });
it('should not replace anything in <pre>, but escape the html escape the content', function(){ it('should not replace anything in <pre>, but escape the html escape the content', function(){
expect(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( toEqual(
'<p>bah x</p>' + '<p>bah x</p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' + '<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
@ -163,7 +139,7 @@ describe('ngdoc', function(){
}); });
it('should replace text between two <pre></pre> tags', 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'); toMatch('</div><h1>One</h1><div');
}); });
@ -340,38 +316,20 @@ describe('ngdoc', function(){
it('should not remove {{}}', function(){ it('should not remove {{}}', function(){
var doc = new Doc('@example text {{ abc }}'); var doc = new Doc('@example text {{ abc }}');
doc.parse(); doc.parse();
expect(doc.example).toEqual('text {{ abc }}'); expect(doc.example).toEqual('<p>text {{ abc }}</p>');
});
});
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');
}); });
it('should alias @exampleDescription to @exampleDesc', function(){ it('should support doc:example', function(){
var doc = new Doc('@exampleDesc some\n text'); var doc = new Doc('@ngdoc overview\n@example \n' +
doc.ngdoc = "filter"; '<doc:example>\n' +
doc.parse(); ' <doc:source><escapeme></doc:source>\n' +
expect(doc.html()).toContain('<p>some\n text'); ' <doc:scenario><scenario></doc:scenario>\n' +
'</doc:example>').parse();
var html = doc.html();
expect(html).toContain('<doc:source>&lt;escapeme&gt;</doc:source>');
expect(html).toContain('<doc:scenario>&lt;scenario&gt;</doc:scenario>');
expect(doc.scenarios).toEqual(['<scenario>']);
}); });
it('should render description in related method', function(){
var doc = new Doc('').parse();
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() { describe('@depricated', function() {

View file

@ -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){ 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.headingDepth++;
this.tag('h' + this.headingDepth, heading); this.tag('h' + this.headingDepth, heading);
var className = typeof heading == 'string' var className = typeof heading == 'string'

View file

@ -7,7 +7,6 @@ var DOM = require('dom.js').DOM;
var htmlEscape = require('dom.js').htmlEscape; var htmlEscape = require('dom.js').htmlEscape;
var NEW_LINE = /\n\r?/; var NEW_LINE = /\n\r?/;
exports.markdown = markdown;
exports.trim = trim; exports.trim = trim;
exports.metadata = metadata; exports.metadata = metadata;
exports.scenarios = scenarios; exports.scenarios = scenarios;
@ -25,6 +24,11 @@ function Doc(text, file, line) {
this.file = file; this.file = file;
this.line = line; 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(){ Doc.METADATA_IGNORE = (function(){
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8'); var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
@ -53,16 +57,53 @@ Doc.prototype = {
return words.join(' '); return words.join(' ');
}, },
markdown: function (text) {
var self = this;
var IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:)/;
var IS_ANGULAR = /^angular\./;
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, '&lt;').replace(/>/g, '&gt;') +
'</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){
self.scenarios.push(content);
return before + htmlEscape(content) + after;
});
} else {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
text = text.replace(/{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/gm,
function(_all, url, _2, _3, title){
return '<a href="' + (url.match(IS_URL) ? '' : '#!') + url + '">'
+ (url.match(IS_ANGULAR) ? '<code>' : '')
+ (title || url)
+ (url.match(IS_ANGULAR) ? '</code>' : '')
+ '</a>';
});
text = new Showdown.converter().makeHtml(text);
}
parts[i] = text;
});
return parts.join('');
},
parse: function(){ parse: function(){
var atName; var atName;
var atText; var atText;
var match; var match;
var self = this; var self = this;
this.scenarios = [];
this.requires = [];
this.param = [];
this.properties = [];
this.methods = [];
self.text.split(NEW_LINE).forEach(function(line){ self.text.split(NEW_LINE).forEach(function(line){
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) { if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
// we found @name ... // we found @name ...
@ -82,9 +123,9 @@ Doc.prototype = {
this.id = this.id // if we have an id just use it 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.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // try to extract it from file name
|| this.name; // default to name || this.name; // default to name
this.description = markdown(this.description); this.description = this.markdown(this.description);
this['this'] = markdown(this['this']); this.example = this.markdown(this.example);
this.exampleDescription = markdown(this.exampleDescription || this.exampleDesc); this['this'] = this.markdown(this['this']);
return this; return this;
function flush(){ function flush(){
@ -98,7 +139,7 @@ Doc.prototype = {
} }
var param = { var param = {
name: match[5] || match[4], 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], type: match[1],
optional: !!match[2], optional: !!match[2],
'default':match[6] 'default':match[6]
@ -111,18 +152,10 @@ Doc.prototype = {
} }
self.returns = { self.returns = {
type: match[1], 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>/gmi,
function(_, scenario){
self.scenarios.push(scenario);
});
self.description = text;
} else if(atName == 'requires') { } else if(atName == 'requires') {
self.requires.push(text); self.requires.push(text);
} else if(atName == 'scenario') {
self.scenarios.push(text);
} else if(atName == 'property') { } else if(atName == 'property') {
var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/); var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
if (!match) { if (!match) {
@ -154,7 +187,7 @@ Doc.prototype = {
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc); throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom); }).call(self, dom);
dom.example(self.exampleDescription, self.example, self.scenarios[0]); dom.h('Example', self.example, dom.html);
}); });
return dom.toString(); return dom.toString();
@ -407,13 +440,13 @@ Doc.prototype = {
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){ dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description); dom.html(method.description);
method.html_usage_parameters(dom); 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('Properties', this.properties, function(property){
dom.h(property.name, function(){ dom.h(property.name, function(){
dom.text(property.description); dom.text(property.description);
dom.example(property.exampleDescription, property.example, false); dom.h('Example', property.example, dom.html);
}); });
}); });
}, },
@ -436,47 +469,6 @@ Doc.prototype = {
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
function markdown (text) {
var IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:)/;
var IS_ANGULAR = /^angular\./;
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, '&lt;').replace(/>/g, '&gt;') +
'</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>&lt;angular/&gt;</tt>');
text = text.replace(/{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/gm,
function(_all, url, _2, _3, title){
return '<a href="' + (url.match(IS_URL) ? '' : '#!') + url + '">'
+ (url.match(IS_ANGULAR) ? '<code>' : '')
+ (title || url)
+ (url.match(IS_ANGULAR) ? '</code>' : '')
+ '</a>';
});
text = new Showdown.converter().makeHtml(text);
}
parts[i] = text;
});
return parts.join('');
};
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
function scenarios(docs){ function scenarios(docs){
var specs = []; var specs = [];

View file

@ -484,14 +484,18 @@ function map(obj, iterator, context) {
* @returns {number} The size of `obj` or `0` if `obj` is neither an object or an array. * @returns {number} The size of `obj` or `0` if `obj` is neither an object or an array.
* *
* @example * @example
* Number of items in array: {{ [1,2].$size() }}<br/> * <doc:example>
* Number of items in object: {{ {a:1, b:2, c:3}.$size() }}<br/> * <doc:source>
* * Number of items in array: {{ [1,2].$size() }}<br/>
* @scenario * Number of items in object: {{ {a:1, b:2, c:3}.$size() }}<br/>
it('should print correct sizes for an array and an object', function() { * </doc:source>
expect(binding('[1,2].$size()')).toBe('2'); * <doc:scenario>
expect(binding('{a:1, b:2, c:3}.$size()')).toBe('3'); * 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) { function size(obj) {
var size = 0, key; var size = 0, key;
@ -556,17 +560,19 @@ function isLeafNode (node) {
* @returns {*} The copy or updated `destination` if `destination` was specified. * @returns {*} The copy or updated `destination` if `destination` was specified.
* *
* @example * @example
Salutation: <input type="text" name="master.salutation" value="Hello" /><br/> * <doc:example>
Name: <input type="text" name="master.name" value="world"/><br/> * <doc:source>
<button ng:click="form = master.$copy()">copy</button> Salutation: <input type="text" name="master.salutation" value="Hello" /><br/>
<hr/> 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>master={{master}}</pre>
<pre>form={{form}}</pre> <pre>form={{form}}</pre>
* </doc:source>
* @scenario * <doc:scenario>
it('should print that initialy the form object is NOT equal to master', function() { 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.salutation]').val()).toBe('Hello');
expect(element('.doc-example input[name=master.name]').val()).toBe('world'); expect(element('.doc-example input[name=master.name]').val()).toBe('world');
@ -577,6 +583,8 @@ function isLeafNode (node) {
element('.doc-example button').click(); element('.doc-example button').click();
expect(element('.doc-example span').css('display')).toBe('none'); expect(element('.doc-example span').css('display')).toBe('none');
}); });
* </doc:scenario>
* </doc:example>
*/ */
function copy(source, destination){ function copy(source, destination){
if (!destination) { if (!destination) {
@ -633,27 +641,31 @@ function copy(source, destination){
* @returns {boolean} True if arguments are equal. * @returns {boolean} True if arguments are equal.
* *
* @example * @example
Salutation: <input type="text" name="greeting.salutation" value="Hello" /><br/> * <doc:example>
Name: <input type="text" name="greeting.name" value="world"/><br/> * <doc:source>
<hr/> 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 The <code>greeting</code> object is
<span ng:hide="greeting.$equals({salutation:'Hello', name:'world'})">NOT</span> equal to <span ng:hide="greeting.$equals({salutation:'Hello', name:'world'})">NOT</span> equal to
<code>{salutation:'Hello', name:'world'}</code>. <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 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');
});
@scenario it('should say that the objects are not equal when the form is modified', function() {
it('should print that initialy greeting is equal to the hardcoded value object', function() { input('greeting.name').enter('kitty');
expect(element('.doc-example input[name=greeting.salutation]').val()).toBe('Hello'); expect(element('.doc-example span').css('display')).toBe('inline');
expect(element('.doc-example input[name=greeting.name]').val()).toBe('world'); });
expect(element('.doc-example span').css('display')).toBe('none'); * </doc:scenario>
}); * </doc:example>
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');
});
*/ */
function equals(o1, o2) { function equals(o1, o2) {
if (o1 == o2) return true; if (o1 == o2) return true;

View file

@ -139,40 +139,44 @@ Compiler.prototype = {
* @element ANY * @element ANY
* @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant * @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 * try changing the invoice and see that the Total will lag in evaluation
* @example * @example
<div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div> <doc:example>
<div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div> <doc:source>
<table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]"> <div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div>
<tr> <div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div>
<td>QTY</td> <table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]">
<td>Description</td> <tr>
<td>Cost</td> <td>QTY</td>
<td>Total</td> <td>Description</td>
<td></td> <td>Cost</td>
</tr> <td>Total</td>
<tr ng:repeat="item in items"> <td></td>
<td><input name="item.qty"/></td> </tr>
<td><input name="item.desc"/></td> <tr ng:repeat="item in items">
<td><input name="item.cost"/></td> <td><input name="item.qty"/></td>
<td>{{item.total = item.qty * item.cost | currency}}</td> <td><input name="item.desc"/></td>
<td><a href="" ng:click="items.$remove(item)">X</a></td> <td><input name="item.cost"/></td>
</tr> <td>{{item.total = item.qty * item.cost | currency}}</td>
<tr> <td><a href="" ng:click="items.$remove(item)">X</a></td>
<td colspan="3"><a href="" ng:click="items.$add()">add</a></td> </tr>
<td>{{ items.$sum('total') | currency }}</td> <tr>
</tr> <td colspan="3"><a href="" ng:click="items.$add()">add</a></td>
</table> <td>{{ items.$sum('total') | currency }}</td>
* </tr>
* @scenario </table>
it('should check ng:format', function(){ </doc:source>
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99'); <doc:scenario>
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99'); it('should check ng:format', function(){
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:first').binding("items.$sum('total')")).toBe('$9.99'); expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99');
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98'); 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){ templatize: function(element, elementIndex, priority){

View file

@ -202,7 +202,7 @@ function errorHandlerFor(element, error) {
* @returns {Object} Newly created scope. * @returns {Object} Newly created scope.
* *
* *
* @exampleDescription * @example
* This example demonstrates scope inheritance and property overriding. * This example demonstrates scope inheritance and property overriding.
* *
* In this example, the root scope encompasses the whole HTML DOM tree. This scope has `salutation`, * 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 child scope inherits the salutation property from the root scope.
* - The $index property does not leak from the child scope to the root scope. * - The $index property does not leak from the child scope to the root scope.
* *
* @example <doc:example>
<ul ng:init="salutation='Hello'; name='Misko'; names=['World', 'Earth']"> <doc:source>
<li ng:repeat="name in names"> <ul ng:init="salutation='Hello'; name='Misko'; names=['World', 'Earth']">
{{$index}}: {{salutation}} {{name}}! <li ng:repeat="name in names">
</li> {{$index}}: {{salutation}} {{name}}!
</ul> </li>
<pre> </ul>
$index={{$index}} <pre>
salutation={{salutation}} $index={{$index}}
name={{name}}</pre> salutation={{salutation}}
name={{name}}</pre>
@scenario </doc:source>
it('should inherit the salutation property and override the name property', function() { <doc:scenario>
expect(using('.doc-example-live').repeater('li').row(0)). it('should inherit the salutation property and override the name property', function() {
toEqual(['0', 'Hello', 'World']); expect(using('.doc-example-live').repeater('li').row(0)).
expect(using('.doc-example-live').repeater('li').row(1)). toEqual(['0', 'Hello', 'World']);
toEqual(['1', 'Hello', 'Earth']); expect(using('.doc-example-live').repeater('li').row(1)).
expect(using('.doc-example-live').element('pre').text()). toEqual(['1', 'Hello', 'Earth']);
toBe('$index=\nsalutation=Hello\nname=Misko'); expect(using('.doc-example-live').element('pre').text()).
}); toBe('$index=\nsalutation=Hello\nname=Misko');
});
</doc:scenario>
</doc:example>
*/ */
function createScope(parent, providers, instanceCache) { function createScope(parent, providers, instanceCache) {
function Parent(){} function Parent(){}

28
src/angular-mocks.js vendored
View file

@ -239,10 +239,10 @@ angular.service('$exceptionHandler', function(e) {
*/ */
angular.service('$log', function() { angular.service('$log', function() {
var $log = { var $log = {
log: function(){ $log.logs.push(arguments) }, log: function(){ $log.logs.push(arguments); },
warn: function(){ $log.logs.push(arguments) }, warn: function(){ $log.logs.push(arguments); },
info: function(){ $log.logs.push(arguments) }, info: function(){ $log.logs.push(arguments); },
error: function(){ $log.logs.push(arguments) } error: function(){ $log.logs.push(arguments); }
}; };
$log.log.logs = []; $log.log.logs = [];
@ -265,15 +265,6 @@ angular.service('$log', function() {
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
* *
* @example * @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 !!!!! * !!!! WARNING !!!!!
* This is not a complete Date object so only methods that were implemented can be called safely. * 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. * To make matters worse, TzDate instances inherit stuff from Date via a prototype.
@ -281,6 +272,17 @@ angular.service('$log', function() {
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is * 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: * incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object". * "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) { function TzDate(offset, timestamp) {
if (angular.isString(timestamp)) { if (angular.isString(timestamp)) {

View file

@ -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. * @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 * @example
<div ng:init="books = ['Moby Dick', 'Great Gatsby', 'Romeo and Juliet']"></div> <doc:example>
<input name='bookName' value='Romeo and Juliet'> <br> <doc:source>
Index of '{{bookName}}' in the list {{books}} is <em>{{books.$indexOf(bookName)}}</em>. <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 recalculate', function() {
it('should correctly calculate the initial index', function() { input('bookName').enter('foo');
expect(binding('books.$indexOf(bookName)')).toBe('2'); expect(binding('books.$indexOf(bookName)')).toBe('-1');
});
it('should recalculate', function() { input('bookName').enter('Moby Dick');
input('bookName').enter('foo'); expect(binding('books.$indexOf(bookName)')).toBe('0');
expect(binding('books.$indexOf(bookName)')).toBe('-1'); });
</doc:scenario>
input('bookName').enter('Moby Dick'); </doc:example>
expect(binding('books.$indexOf(bookName)')).toBe('0');
});
*/ */
'indexOf': indexOf, 'indexOf': indexOf,
@ -117,42 +121,46 @@ var angularArray = {
* @returns {number} Sum of items in the array. * @returns {number} Sum of items in the array.
* *
* @example * @example
<table ng:init="invoice= {items:[{qty:10, description:'gadget', cost:9.95}]}"> <doc:example>
<tr><th>Qty</th><th>Description</th><th>Cost</th><th>Total</th><th></th></tr> <doc:source>
<tr ng:repeat="item in invoice.items"> <table ng:init="invoice= {items:[{qty:10, description:'gadget', cost:9.95}]}">
<td><input name="item.qty" value="1" size="4" ng:required ng:validate="integer"></td> <tr><th>Qty</th><th>Description</th><th>Cost</th><th>Total</th><th></th></tr>
<td><input name="item.description"></td> <tr ng:repeat="item in invoice.items">
<td><input name="item.cost" value="0.00" ng:required ng:validate="number" size="6"></td> <td><input name="item.qty" value="1" size="4" ng:required ng:validate="integer"></td>
<td>{{item.qty * item.cost | currency}}</td> <td><input name="item.description"></td>
<td>[<a href ng:click="invoice.items.$remove(item)">X</a>]</td> <td><input name="item.cost" value="0.00" ng:required ng:validate="number" size="6"></td>
</tr> <td>{{item.qty * item.cost | currency}}</td>
<tr> <td>[<a href ng:click="invoice.items.$remove(item)">X</a>]</td>
<td><a href ng:click="invoice.items.$add()">add item</a></td> </tr>
<td></td> <tr>
<td>Total:</td> <td><a href ng:click="invoice.items.$add()">add item</a></td>
<td>{{invoice.items.$sum('qty*cost') | currency}}</td> <td></td>
</tr> <td>Total:</td>
</table> <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 it('should add an entry and recalculate', function() {
//TODO: these specs are lame because I had to work around issues #164 and #167 element('.doc-example a:contains("add item")').click();
it('should initialize and calculate the totals', function() { using('.doc-example-live tr:nth-child(3)').input('item.qty').enter('20');
expect(repeater('.doc-example-live table tr', 'item in invoice.items').count()).toBe(3); 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(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() { expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(2)).
element('.doc-example a:contains("add item")').click(); toEqual(['$2,000.00']);
using('.doc-example-live tr:nth-child(3)').input('item.qty').enter('20'); expect(binding("invoice.items.$sum('qty*cost')")).toBe('$2,099.50');
using('.doc-example-live tr:nth-child(3)').input('item.cost').enter('100'); });
</doc:scenario>
expect(repeater('.doc-example-live table tr', 'item in invoice.items').row(2)). </doc:example>
toEqual(['$2,000.00']);
expect(binding("invoice.items.$sum('qty*cost')")).toBe('$2,099.50');
});
*/ */
'sum':function(array, expression) { 'sum':function(array, expression) {
var fn = angular['Function']['compile'](expression); var fn = angular['Function']['compile'](expression);
@ -185,33 +193,37 @@ var angularArray = {
* @returns {*} The removed element. * @returns {*} The removed element.
* *
* @example * @example
<ul ng:init="tasks=['Learn Angular', 'Read Documentation', <doc:example>
'Check out demos', 'Build cool applications']"> <doc:source>
<li ng:repeat="task in tasks"> <ul ng:init="tasks=['Learn Angular', 'Read Documentation',
{{task}} [<a href="" ng:click="tasks.$remove(task)">X</a>] 'Check out demos', 'Build cool applications']">
</li> <li ng:repeat="task in tasks">
</ul> {{task}} [<a href="" ng:click="tasks.$remove(task)">X</a>]
<hr/> </li>
tasks = {{tasks}} </ul>
<hr/>
tasks = {{tasks}}
</doc:source>
<doc: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']);
});
@scenario it('should initialize the task list with for tasks', function() {
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(4); expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(3);
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 ul li a:contains("X"):last').click();
element('.doc-example ul li a:contains("X"):first').click(); expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(2);
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(3);
element('.doc-example ul li a:contains("X"):last').click(); expect(repeater('.doc-example ul li', 'task in tasks').column('task')).
expect(repeater('.doc-example ul li', 'task in tasks').count()).toBe(2); toEqual(['Read Documentation', 'Check out demos']);
});
expect(repeater('.doc-example ul li', 'task in tasks').column('task')). </doc:scenario>
toEqual(['Read Documentation', 'Check out demos']); </doc:example>
});
*/ */
'remove':function(array, value) { 'remove':function(array, value) {
var index = indexOf(array, value); var index = indexOf(array, value);
@ -254,48 +266,52 @@ var angularArray = {
* the predicate returned true for. * the predicate returned true for.
* *
* @example * @example
<div ng:init="friends = [{name:'John', phone:'555-1276'}, <doc:example>
{name:'Mary', phone:'800-BIG-MARY'}, <doc:source>
{name:'Mike', phone:'555-4321'}, <div ng:init="friends = [{name:'John', phone:'555-1276'},
{name:'Adam', phone:'555-5678'}, {name:'Mary', phone:'800-BIG-MARY'},
{name:'Julie', phone:'555-8765'}]"></div> {name:'Mike', phone:'555-4321'},
{name:'Adam', phone:'555-5678'},
{name:'Julie', phone:'555-8765'}]"></div>
Search: <input name="searchText"/> Search: <input name="searchText"/>
<table id="searchTextResults"> <table id="searchTextResults">
<tr><th>Name</th><th>Phone</th><tr> <tr><th>Name</th><th>Phone</th><tr>
<tr ng:repeat="friend in friends.$filter(searchText)"> <tr ng:repeat="friend in friends.$filter(searchText)">
<td>{{friend.name}}</td> <td>{{friend.name}}</td>
<td>{{friend.phone}}</td> <td>{{friend.phone}}</td>
<tr> <tr>
</table> </table>
<hr> <hr>
Any: <input name="search.$"/> <br> Any: <input name="search.$"/> <br>
Name only <input name="search.name"/><br> Name only <input name="search.name"/><br>
Phone only <input name="search.phone"/><br> Phone only <input name="search.phone"/><br>
<table id="searchObjResults"> <table id="searchObjResults">
<tr><th>Name</th><th>Phone</th><tr> <tr><th>Name</th><th>Phone</th><tr>
<tr ng:repeat="friend in friends.$filter(search)"> <tr ng:repeat="friend in friends.$filter(search)">
<td>{{friend.name}}</td> <td>{{friend.name}}</td>
<td>{{friend.phone}}</td> <td>{{friend.phone}}</td>
<tr> <tr>
</table> </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 input('searchText').enter('76');
it('should search across all fields when filtering with a string', function() { expect(repeater('#searchTextResults tr', 'friend in friends').column('name')).
input('searchText').enter('m'); toEqual(['John', 'Julie']);
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). });
toEqual(['Mary', 'Mike', 'Adam']);
input('searchText').enter('76'); it('should search in specific fields when filtering with a predicate object', function() {
expect(repeater('#searchTextResults tr', 'friend in friends').column('name')). input('search.$').enter('i');
toEqual(['John', 'Julie']); 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() { </doc:scenario>
input('search.$').enter('i'); </doc:example>
expect(repeater('#searchObjResults tr', 'friend in friends').column('name')).
toEqual(['Mary', 'Mike', 'Julie']);
});
*/ */
'filter':function(array, expression) { 'filter':function(array, expression) {
var predicates = []; var predicates = [];
@ -398,53 +414,55 @@ var angularArray = {
* *
* @TODO simplify the example. * @TODO simplify the example.
* *
* @exampleDescription * @example
* This example shows how an initially empty array can be filled with objects created from user * This example shows how an initially empty array can be filled with objects created from user
* input via the `$add` method. * input via the `$add` method.
* <doc:example>
* @example <doc:source>
[<a href="" ng:click="people.$add()">add empty</a>] [<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:'John', sex:'male'})">add 'John'</a>]
[<a href="" ng:click="people.$add({name:'Mary', sex:'female'})">add 'Mary'</a>] [<a href="" ng:click="people.$add({name:'Mary', sex:'female'})">add 'Mary'</a>]
<ul ng:init="people=[]"> <ul ng:init="people=[]">
<li ng:repeat="person in people"> <li ng:repeat="person in people">
<input name="person.name"> <input name="person.name">
<select name="person.sex"> <select name="person.sex">
<option value="">--chose one--</option> <option value="">--chose one--</option>
<option>male</option> <option>male</option>
<option>female</option> <option>female</option>
</select> </select>
[<a href="" ng:click="people.$remove(person)">X</a>] [<a href="" ng:click="people.$remove(person)">X</a>]
</li> </li>
</ul> </ul>
<pre>people = {{people}}</pre> <pre>people = {{people}}</pre>
</doc:source>
<doc:scenario>
beforeEach(function() {
expect(binding('people')).toBe('people = []');
});
@scenario it('should create an empty record when "add empty" is clicked', function() {
beforeEach(function() { element('.doc-example a:contains("add empty")').click();
expect(binding('people')).toBe('people = []'); expect(binding('people')).toBe('people = [{\n "name":"",\n "sex":null}]');
}); });
it('should create an empty record when "add empty" is clicked', function() { it('should create a "John" record when "add \'John\'" is clicked', function() {
element('.doc-example a:contains("add empty")').click(); element('.doc-example a:contains("add \'John\'")').click();
expect(binding('people')).toBe('people = [{\n "name":"",\n "sex":null}]'); expect(binding('people')).toBe('people = [{\n "name":"John",\n "sex":"male"}]');
}); });
it('should create a "John" record when "add \'John\'" is clicked', function() { it('should create a "Mary" record when "add \'Mary\'" is clicked', function() {
element('.doc-example a:contains("add \'John\'")').click(); element('.doc-example a:contains("add \'Mary\'")').click();
expect(binding('people')).toBe('people = [{\n "name":"John",\n "sex":"male"}]'); expect(binding('people')).toBe('people = [{\n "name":"Mary",\n "sex":"female"}]');
}); });
it('should create a "Mary" record when "add \'Mary\'" is clicked', function() { it('should delete a record when "X" is clicked', function() {
element('.doc-example a:contains("add \'Mary\'")').click(); element('.doc-example a:contains("add empty")').click();
expect(binding('people')).toBe('people = [{\n "name":"Mary",\n "sex":"female"}]'); element('.doc-example li a:contains("X"):first').click();
}); expect(binding('people')).toBe('people = []');
});
it('should delete a record when "X" is clicked', function() { </doc:scenario>
element('.doc-example a:contains("add empty")').click(); </doc:example>
element('.doc-example li a:contains("X"):first').click();
expect(binding('people')).toBe('people = []');
});
*/ */
'add':function(array, value) { 'add':function(array, value) {
array.push(isUndefined(value)? {} : 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). * @returns {number} Number of elements in the array (for which the condition evaluates to true).
* *
* @example * @example
<pre ng:init="items = [{name:'knife', points:1}, <doc:example>
{name:'fork', points:3}, <doc:source>
{name:'spoon', points:1}]"></pre> <pre ng:init="items = [{name:'knife', points:1},
<ul> {name:'fork', points:3},
<li ng:repeat="item in items"> {name:'spoon', points:1}]"></pre>
{{item.name}}: points= <ul>
<input type="text" name="item.points"/> <!-- id="item{{$index}} --> <li ng:repeat="item in items">
</li> {{item.name}}: points=
</ul> <input type="text" name="item.points"/> <!-- id="item{{$index}} -->
<p>Number of items which have one point: <em>{{ items.$count('points==1') }}</em></p> </li>
<p>Number of items which have more than one point: <em>{{items.$count('points&gt;1')}}</em></p> </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&gt;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 recalculate when updated', function() {
it('should calculate counts', function() { using('.doc-example li:first-child').input('item.points').enter('23');
expect(binding('items.$count(\'points==1\')')).toEqual(2); expect(binding('items.$count(\'points==1\')')).toEqual(1);
expect(binding('items.$count(\'points>1\')')).toEqual(1); expect(binding('items.$count(\'points>1\')')).toEqual(2);
}); });
</doc:scenario>
it('should recalculate when updated', function() { </doc:example>
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);
});
*/ */
'count':function(array, condition) { 'count':function(array, condition) {
if (!condition) return array.length; if (!condition) return array.length;
@ -535,52 +557,56 @@ var angularArray = {
* @returns {Array} Sorted copy of the source array. * @returns {Array} Sorted copy of the source array.
* *
* @example * @example
<div ng:init="friends = [{name:'John', phone:'555-1212', age:10}, <doc:example>
{name:'Mary', phone:'555-9876', age:19}, <doc:source>
{name:'Mike', phone:'555-4321', age:21}, <div ng:init="friends = [{name:'John', phone:'555-1212', age:10},
{name:'Adam', phone:'555-5678', age:35}, {name:'Mary', phone:'555-9876', age:19},
{name:'Julie', phone:'555-8765', age:29}]"></div> {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> <pre>Sorting predicate = {{predicate}}</pre>
<hr/> <hr/>
<table ng:init="predicate='-age'"> <table ng:init="predicate='-age'">
<tr> <tr>
<th><a href="" ng:click="predicate = 'name'">Name</a> <th><a href="" ng:click="predicate = 'name'">Name</a>
(<a href ng:click="predicate = '-name'">^</a>)</th> (<a href ng:click="predicate = '-name'">^</a>)</th>
<th><a href="" ng:click="predicate = 'phone'">Phone</a> <th><a href="" ng:click="predicate = 'phone'">Phone</a>
(<a href ng:click="predicate = '-phone'">^</a>)</th> (<a href ng:click="predicate = '-phone'">^</a>)</th>
<th><a href="" ng:click="predicate = 'age'">Age</a> <th><a href="" ng:click="predicate = 'age'">Age</a>
(<a href ng:click="predicate = '-age'">^</a>)</th> (<a href ng:click="predicate = '-age'">^</a>)</th>
<tr> <tr>
<tr ng:repeat="friend in friends.$orderBy(predicate)"> <tr ng:repeat="friend in friends.$orderBy(predicate)">
<td>{{friend.name}}</td> <td>{{friend.name}}</td>
<td>{{friend.phone}}</td> <td>{{friend.phone}}</td>
<td>{{friend.age}}</td> <td>{{friend.age}}</td>
<tr> <tr>
</table> </table>
</doc:source>
<doc: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']);
});
@scenario it('should reorder the table when user selects different predicate', function() {
it('should be reverse ordered by aged', function() { element('.doc-example a:contains("Name")').click();
expect(binding('predicate')).toBe('Sorting predicate = -age'); expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
expect(repeater('.doc-example table', 'friend in friends').column('friend.age')). toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
toEqual(['35', '29', '21', '19', '10']); expect(repeater('.doc-example table', 'friend in friends').column('friend.age')).
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')). toEqual(['35', '10', '29', '19', '21']);
toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
});
it('should reorder the table when user selects different predicate', function() { element('.doc-example a:contains("Phone")+a:contains("^")').click();
element('.doc-example a:contains("Name")').click(); expect(repeater('.doc-example table', 'friend in friends').column('friend.phone')).
expect(repeater('.doc-example table', 'friend in friends').column('friend.name')). toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); expect(repeater('.doc-example table', 'friend in friends').column('friend.name')).
expect(repeater('.doc-example table', 'friend in friends').column('friend.age')). toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
toEqual(['35', '10', '29', '19', '21']); });
</doc:scenario>
element('.doc-example a:contains("Phone")+a:contains("^")').click(); </doc:example>
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']);
});
*/ */
//TODO: WTH is descend param for and how/when it should be used, how is it affected by +/- in //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. // predicate? the code below is impossible to read and specs are not very good.
@ -648,21 +674,25 @@ var angularArray = {
* @returns {Array} A new sub-array of length `limit`. * @returns {Array} A new sub-array of length `limit`.
* *
* @example * @example
<div ng:init="numbers = [1,2,3,4,5,6,7,8,9]"> <doc:example>
Limit [1,2,3,4,5,6,7,8,9] to: <input name="limit" value="3"/> <doc:source>
<p>Output: {{ numbers.$limitTo(limit) | json }}</p> <div ng:init="numbers = [1,2,3,4,5,6,7,8,9]">
</div> 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 input[name=limit]').val()).toBe('3');
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3]');
});
* @scenario it('should update the output when -3 is entered', function() {
it('should limit the numer array to first three items', function() { input('limit').enter(-3);
expect(element('.doc-example input[name=limit]').val()).toBe('3'); expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]');
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[1,2,3]'); });
}); </doc:scenario>
</doc:example>
it('should update the output when -3 is entered', function() {
input('limit').enter(-3);
expect(binding('numbers.$limitTo(limit) | json')).toEqual('[7,8,9]');
});
*/ */
limitTo: function(array, limit) { limitTo: function(array, limit) {
limit = parseInt(limit, 10); limit = parseInt(limit, 10);

View file

@ -11,15 +11,19 @@
* @param {expression} expression to eval. * @param {expression} expression to eval.
* *
* @example * @example
<doc:example>
<doc:source>
<div ng:init="greeting='Hello'; person='World'"> <div ng:init="greeting='Hello'; person='World'">
{{greeting}} {{person}}! {{greeting}} {{person}}!
</div> </div>
* </doc:source>
* @scenario <doc:scenario>
it('should check greeting', function(){ it('should check greeting', function(){
expect(binding('greeting')).toBe('Hello'); expect(binding('greeting')).toBe('Hello');
expect(binding('person')).toBe('World'); expect(binding('person')).toBe('World');
}); });
</doc:scenario>
</doc:example>
*/ */
angularDirective("ng:init", function(expression){ angularDirective("ng:init", function(expression){
return function(element){ return function(element){
@ -42,57 +46,61 @@ angularDirective("ng:init", function(expression){
* @param {expression} expression to eval. * @param {expression} expression to eval.
* *
* @example * @example
<script type="text/javascript"> <doc:example>
function SettingsController() { <doc:source>
this.name = "John Smith"; <script type="text/javascript">
this.contacts = [ function SettingsController() {
{type:'phone', value:'408 555 1212'}, this.name = "John Smith";
{type:'email', value:'john.smith@example.org'} ]; this.contacts = [
} {type:'phone', value:'408 555 1212'},
SettingsController.prototype = { {type:'email', value:'john.smith@example.org'} ];
greet: function(){ }
alert(this.name); SettingsController.prototype = {
}, greet: function(){
addContact: function(){ alert(this.name);
this.contacts.push({type:'email', value:'yourname@example.org'}); },
}, addContact: function(){
removeContact: function(contactToRemove) { this.contacts.push({type:'email', value:'yourname@example.org'});
angular.Array.remove(this.contacts, contactToRemove); },
}, removeContact: function(contactToRemove) {
clearContact: function(contact) { angular.Array.remove(this.contacts, contactToRemove);
contact.type = 'phone'; },
contact.value = ''; clearContact: function(contact) {
} contact.type = 'phone';
}; contact.value = '';
</script> }
<div ng:controller="SettingsController"> };
Name: <input type="text" name="name"/> </script>
[ <a href="" ng:click="greet()">greet</a> ]<br/> <div ng:controller="SettingsController">
Contact: Name: <input type="text" name="name"/>
<ul> [ <a href="" ng:click="greet()">greet</a> ]<br/>
<li ng:repeat="contact in contacts"> Contact:
<select name="contact.type"> <ul>
<option>phone</option> <li ng:repeat="contact in contacts">
<option>email</option> <select name="contact.type">
</select> <option>phone</option>
<input type="text" name="contact.value"/> <option>email</option>
[ <a href="" ng:click="clearContact(contact)">clear</a> </select>
| <a href="" ng:click="removeContact(contact)">X</a> ] <input type="text" name="contact.value"/>
</li> [ <a href="" ng:click="clearContact(contact)">clear</a>
<li>[ <a href="" ng:click="addContact()">add</a> ]</li> | <a href="" ng:click="removeContact(contact)">X</a> ]
</ul> </li>
</div> <li>[ <a href="" ng:click="addContact()">add</a> ]</li>
* </ul>
* @scenario </div>
it('should check controller', function(){ </doc:source>
expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); <doc:scenario>
expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212'); it('should check controller', function(){
expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org'); expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
element('.doc-example-live li:first a:contains("clear")').click(); expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212');
expect(element('.doc-example-live li:first input').val()).toBe(''); expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org');
element('.doc-example-live li:last a:contains("add")').click(); element('.doc-example-live li:first a:contains("clear")').click();
expect(element('.doc-example-live li[ng\\:repeat-index="2"] input').val()).toBe('yourname@example.org'); 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){ angularDirective("ng:controller", function(expression){
this.scope(true); this.scope(true);
@ -118,30 +126,32 @@ angularDirective("ng:controller", function(expression){
* @element ANY * @element ANY
* @param {expression} expression to eval. * @param {expression} expression to eval.
* *
* @exampleDescription * @example
* Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning * 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, * 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 * 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 user. In such a case `ng:eval` allows you to execute code without updating
* the display. * the display.
* <doc:example>
* @example <doc:source>
* <input name="obj.a" value="6" > <input name="obj.a" value="6" >
* * <input name="obj.b" value="2"> * <input name="obj.b" value="2">
* = {{obj.multiplied = obj.a * obj.b}} <br> = {{obj.multiplied = obj.a * obj.b}} <br>
* <span ng:eval="obj.divide = obj.a / obj.b"></span> <span ng:eval="obj.divide = obj.a / obj.b"></span>
* <span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"></span> <span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"></span>
* <tt>obj.divide = {{obj.divide}}</tt><br/> <tt>obj.divide = {{obj.divide}}</tt><br/>
* <tt>obj.updateCount = {{obj.updateCount}}</tt> <tt>obj.updateCount = {{obj.updateCount}}</tt>
* </doc:source>
* @scenario <doc:scenario>
it('should check eval', function(){ it('should check eval', function(){
expect(binding('obj.divide')).toBe('3'); expect(binding('obj.divide')).toBe('3');
expect(binding('obj.updateCount')).toBe('2'); expect(binding('obj.updateCount')).toBe('2');
input('obj.a').enter('12'); input('obj.a').enter('12');
expect(binding('obj.divide')).toBe('6'); expect(binding('obj.divide')).toBe('6');
expect(binding('obj.updateCount')).toBe('3'); expect(binding('obj.updateCount')).toBe('3');
}); });
</doc:scenario>
</doc:example>
*/ */
angularDirective("ng:eval", function(expression){ angularDirective("ng:eval", function(expression){
return function(element){ return function(element){
@ -164,18 +174,21 @@ angularDirective("ng:eval", function(expression){
* @element ANY * @element ANY
* @param {expression} expression to eval. * @param {expression} expression to eval.
* *
* @exampleDescription
* Try it here: enter text in text box and watch the greeting change.
* @example * @example
* Enter name: <input type="text" name="name" value="Whirled">. <br> * Try it here: enter text in text box and watch the greeting change.
* Hello <span ng:bind="name" />! <doc:example>
* <doc:source>
* @scenario Enter name: <input type="text" name="name" value="Whirled">. <br>
it('should check ng:bind', function(){ Hello <span ng:bind="name" />!
expect(using('.doc-example-live').binding('name')).toBe('Whirled'); </doc:source>
using('.doc-example-live').input('name').enter('world'); <doc:scenario>
expect(using('.doc-example-live').binding('name')).toBe('world'); 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){ angularDirective("ng:bind", function(expression, element){
element.addClass('ng-binding'); element.addClass('ng-binding');
@ -269,22 +282,25 @@ function compileBindTemplate(template){
* @param {string} template of form * @param {string} template of form
* <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval.
* *
* @exampleDescription
* Try it here: enter text in text box and watch the greeting change.
* @example * @example
Salutation: <input type="text" name="salutation" value="Hello"><br/> * Try it here: enter text in text box and watch the greeting change.
Name: <input type="text" name="name" value="World"><br/> <doc:example>
<pre ng:bind-template="{{salutation}} {{name}}!"></pre> <doc:source>
* Salutation: <input type="text" name="salutation" value="Hello"><br/>
* @scenario Name: <input type="text" name="name" value="World"><br/>
it('should check ng:bind', function(){ <pre ng:bind-template="{{salutation}} {{name}}!"></pre>
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')). </doc:source>
toBe('Hello World!'); <doc:scenario>
using('.doc-example-live').input('salutation').enter('Greetings'); it('should check ng:bind', function(){
using('.doc-example-live').input('name').enter('user'); expect(using('.doc-example-live').binding('{{salutation}} {{name}}')).
expect(using('.doc-example-live').binding('{{salutation}} {{name}}')). toBe('Hello World!');
toBe('Greetings user!'); 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){ angularDirective("ng:bind-template", function(expression, element){
element.addClass('ng-binding'); element.addClass('ng-binding');
@ -341,21 +357,24 @@ var REMOVE_ATTRIBUTES = {
* <tt ng:non-bindable>{{expression}}</tt>s. Any number of * <tt ng:non-bindable>{{expression}}</tt>s. Any number of
* key-value pairs can be specified. * key-value pairs can be specified.
* *
* @exampleDescription
* Try it here: enter text in text box and click Google.
* @example * @example
Google for: * Try it here: enter text in text box and click Google.
<input type="text" name="query" value="AngularJS"/> <doc:example>
<a href="http://www.google.com/search?q={{query}}">Google</a> <doc:source>
* Google for:
* @scenario <input type="text" name="query" value="AngularJS"/>
it('should check ng:bind-attr', function(){ <a href="http://www.google.com/search?q={{query}}">Google</a>
expect(using('.doc-example-live').element('a').attr('href')). </doc:source>
toBe('http://www.google.com/search?q=AngularJS'); <doc:scenario>
using('.doc-example-live').input('query').enter('google'); it('should check ng:bind-attr', function(){
expect(using('.doc-example-live').element('a').attr('href')). expect(using('.doc-example-live').element('a').attr('href')).
toBe('http://www.google.com/search?q=google'); 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){ angularDirective("ng:bind-attr", function(expression){
return function(element){ return function(element){
@ -403,16 +422,21 @@ angularDirective("ng:bind-attr", function(expression){
* @param {expression} expression to eval upon click. * @param {expression} expression to eval upon click.
* *
* @example * @example
<button ng:click="count = count + 1" ng:init="count=0"> <doc:example>
Increment <doc:source>
</button> <button ng:click="count = count + 1" ng:init="count=0">
count: {{count}} Increment
* @scenario </button>
it('should check ng:click', function(){ count: {{count}}
expect(binding('count')).toBe('0'); </doc:source>
element('.doc-example-live :button').click(); <doc:scenario>
expect(binding('count')).toBe('1'); 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 * A directive that allows creation of custom onclick handlers that are defined as angular
@ -440,35 +464,37 @@ angularDirective("ng:click", function(expression, element){
* @name angular.directive.ng:submit * @name angular.directive.ng:submit
* *
* @description * @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. * Enables binding angular expressions to onsubmit events.
* *
* Additionally it prevents the default action (which for form means sending the request to the * Additionally it prevents the default action (which for form means sending the request to the
* server and reloading the current page). * server and reloading the current page).
*
* @element form
* @param {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) { angularDirective("ng:submit", function(expression, element) {
return injectUpdateView(function($updateView, element) { return injectUpdateView(function($updateView, element) {
@ -494,20 +520,24 @@ angularDirective("ng:submit", function(expression, element) {
* @element ANY * @element ANY
* @param {expression} expression to eval. * @param {expression} expression to eval.
* *
* @exampleDescription * @example
* Notice that the counter is incremented * Notice that the counter is incremented
* every time you change the text. * every time you change the text.
* @example <doc:example>
<div ng:init="counter=0" ng:watch="name: counter = counter+1"> <doc:source>
<input type="text" name="name" value="hello"><br/> <div ng:init="counter=0" ng:watch="name: counter = counter+1">
Change counter: {{counter}} Name: {{name}} <input type="text" name="name" value="hello"><br/>
</div> Change counter: {{counter}} Name: {{name}}
* @scenario </div>
it('should check ng:watch', function(){ </doc:source>
expect(using('.doc-example-live').binding('counter')).toBe('2'); <doc:scenario>
using('.doc-example-live').input('name').enter('abc'); it('should check ng:watch', function(){
expect(using('.doc-example-live').binding('counter')).toBe('3'); 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');
});
</doc:scenario>
</doc:example>
*/ */
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular) //TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
angularDirective("ng:watch", function(expression, element){ angularDirective("ng:watch", function(expression, element){
@ -550,28 +580,31 @@ function ngClass(selector) {
* @element ANY * @element ANY
* @param {expression} expression to eval. * @param {expression} expression to eval.
* *
* @exampleDescription
* @example * @example
<input type="button" value="set" ng:click="myVar='ng-input-indicator-wait'"> <doc:example>
<input type="button" value="clear" ng:click="myVar=''"> <doc:source>
<br> <input type="button" value="set" ng:click="myVar='ng-input-indicator-wait'">
<span ng:class="myVar">Sample Text &nbsp;&nbsp;&nbsp;&nbsp;</span> <input type="button" value="clear" ng:click="myVar=''">
* <br>
* @scenario <span ng:class="myVar">Sample Text &nbsp;&nbsp;&nbsp;&nbsp;</span>
it('should check ng:class', function(){ </doc:source>
expect(element('.doc-example-live span').attr('className')).not(). <doc:scenario>
toMatch(/ng-input-indicator-wait/); 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')). expect(element('.doc-example-live span').attr('className')).
toMatch(/ng-input-indicator-wait/); toMatch(/ng-input-indicator-wait/);
using('.doc-example-live').element(':button:last').click(); using('.doc-example-live').element(':button:last').click();
expect(element('.doc-example-live span').attr('className')).not(). expect(element('.doc-example-live span').attr('className')).not().
toMatch(/ng-input-indicator-wait/); toMatch(/ng-input-indicator-wait/);
}); });
</doc:scenario>
</doc:example>
*/ */
angularDirective("ng:class", ngClass(function(){return true;})); angularDirective("ng:class", ngClass(function(){return true;}));
@ -588,26 +621,28 @@ angularDirective("ng:class", ngClass(function(){return true;}));
* @element ANY * @element ANY
* @param {expression} expression to eval. Must be inside * @param {expression} expression to eval. Must be inside
* `ng:repeat`. * `ng:repeat`.
* *
* @exampleDescription
* @example * @example
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> <doc:example>
<li ng:repeat="name in names"> <doc:source>
<span ng:class-odd="'ng-format-negative'" <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
ng:class-even="'ng-input-indicator-wait'"> <li ng:repeat="name in names">
{{name}} &nbsp; &nbsp; &nbsp; <span ng:class-odd="'ng-format-negative'"
</span> ng:class-even="'ng-input-indicator-wait'">
</li> {{name}} &nbsp; &nbsp; &nbsp;
</ol> </span>
* </li>
* @scenario </ol>
it('should check ng:class-odd and ng:class-even', function(){ </doc:source>
expect(element('.doc-example-live li:first span').attr('className')). <doc:scenario>
toMatch(/ng-format-negative/); it('should check ng:class-odd and ng:class-even', function(){
expect(element('.doc-example-live li:last span').attr('className')). expect(element('.doc-example-live li:first span').attr('className')).
toMatch(/ng-input-indicator-wait/); 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;})); angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
@ -624,26 +659,28 @@ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
* @element ANY * @element ANY
* @param {expression} expression to eval. Must be inside * @param {expression} expression to eval. Must be inside
* `ng:repeat`. * `ng:repeat`.
* *
* @exampleDescription
* @example * @example
<ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> <doc:example>
<li ng:repeat="name in names"> <doc:source>
<span ng:class-odd="'ng-format-negative'" <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']">
ng:class-even="'ng-input-indicator-wait'"> <li ng:repeat="name in names">
{{name}} &nbsp; &nbsp; &nbsp; <span ng:class-odd="'ng-format-negative'"
</span> ng:class-even="'ng-input-indicator-wait'">
</li> {{name}} &nbsp; &nbsp; &nbsp;
</ol> </span>
* </li>
* @scenario </ol>
it('should check ng:class-odd and ng:class-even', function(){ </doc:source>
expect(element('.doc-example-live li:first span').attr('className')). <doc:scenario>
toMatch(/ng-format-negative/); it('should check ng:class-odd and ng:class-even', function(){
expect(element('.doc-example-live li:last span').attr('className')). expect(element('.doc-example-live li:first span').attr('className')).
toMatch(/ng-input-indicator-wait/); 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;})); angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
@ -660,22 +697,25 @@ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
* @param {expression} expression if truthy then the element is * @param {expression} expression if truthy then the element is
* shown or hidden respectively. * shown or hidden respectively.
* *
* @exampleDescription
* @example * @example
Click me: <input type="checkbox" name="checked"><br/> <doc:example>
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/> <doc:source>
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span> Click me: <input type="checkbox" name="checked"><br/>
* Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
* @scenario Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
it('should check ng:show / ng:hide', function(){ </doc:source>
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); <doc:scenario>
expect(element('.doc-example-live span:last:visible').count()).toEqual(1); 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(); input('checked').check();
expect(element('.doc-example-live span:first:visible').count()).toEqual(1); expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
}); });
</doc:scenario>
</doc:example>
*/ */
angularDirective("ng:show", function(expression, element){ angularDirective("ng:show", function(expression, element){
return function(element){ return function(element){
@ -698,22 +738,25 @@ angularDirective("ng:show", function(expression, element){
* @param {expression} expression if truthy then the element is * @param {expression} expression if truthy then the element is
* shown or hidden respectively. * shown or hidden respectively.
* *
* @exampleDescription
* @example * @example
Click me: <input type="checkbox" name="checked"><br/> <doc:example>
Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/> <doc:source>
Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span> Click me: <input type="checkbox" name="checked"><br/>
* Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/>
* @scenario Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span>
it('should check ng:show / ng:hide', function(){ </doc:source>
expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); <doc:scenario>
expect(element('.doc-example-live span:last:visible').count()).toEqual(1); 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(); input('checked').check();
expect(element('.doc-example-live span:first:visible').count()).toEqual(1); expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
}); });
</doc:scenario>
</doc:example>
*/ */
angularDirective("ng:hide", function(expression, element){ angularDirective("ng:hide", function(expression, element){
return function(element){ return function(element){
@ -736,22 +779,25 @@ angularDirective("ng:hide", function(expression, element){
* CSS style names and values are coresponding values for those * CSS style names and values are coresponding values for those
* CSS keys. * CSS keys.
* *
* @exampleDescription
* @example * @example
<input type="button" value="set" ng:click="myStyle={color:'red'}"> <doc:example>
<input type="button" value="clear" ng:click="myStyle={}"> <doc:source>
<br/> <input type="button" value="set" ng:click="myStyle={color:'red'}">
<span ng:style="myStyle">Sample Text</span> <input type="button" value="clear" ng:click="myStyle={}">
<pre>myStyle={{myStyle}}</pre> <br/>
* <span ng:style="myStyle">Sample Text</span>
* @scenario <pre>myStyle={{myStyle}}</pre>
it('should check ng:style', function(){ </doc:source>
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); <doc:scenario>
element('.doc-example-live :button[value=set]').click(); it('should check ng:style', function(){
expect(element('.doc-example-live span').css('color')).toBe('red'); expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
element('.doc-example-live :button[value=clear]').click(); element('.doc-example-live :button[value=set]').click();
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); 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){ angularDirective("ng:style", function(expression, element){
return function(element){ return function(element){

View file

@ -14,19 +14,23 @@
* When the value is negative, this css class is applied to the binding making it by default red. * When the value is negative, this css class is applied to the binding making it by default red.
* *
* @example * @example
<input type="text" name="amount" value="1234.56"/> <br/> <doc:example>
{{amount | currency}} <doc:source>
* <input type="text" name="amount" value="1234.56"/> <br/>
* @scenario {{amount | currency}}
it('should init with 1234.56', function(){ </doc:source>
expect(binding('amount | currency')).toBe('$1,234.56'); <doc:scenario>
}); it('should init with 1234.56', function(){
it('should update', function(){ expect(binding('amount | currency')).toBe('$1,234.56');
input('amount').enter('-1234'); });
expect(binding('amount | currency')).toBe('$-1,234.00'); it('should update', function(){
expect(element('.doc-example-live .ng-binding').attr('className')). input('amount').enter('-1234');
toMatch(/ng-format-negative/); 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){ angularFilter.currency = function(amount){
this.$element.toggleClass('ng-format-negative', amount < 0); 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. * @returns {string} Number rounded to decimalPlaces and places a , after each third digit.
* *
* @example * @example
Enter number: <input name='val' value='1234.56789' /><br/> <doc:example>
Default formatting: {{val | number}}<br/> <doc:source>
No fractions: {{val | number:0}}<br/> Enter number: <input name='val' value='1234.56789' /><br/>
Negative number: {{-val | number:4}} 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 update', function(){
it('should format numbers', function(){ input('val').enter('3374.333');
expect(binding('val | number')).toBe('1,234.57'); expect(binding('val | number')).toBe('3,374.33');
expect(binding('val | number:0')).toBe('1,235'); expect(binding('val | number:0')).toBe('3,374');
expect(binding('-val | number:4')).toBe('-1,234.5679'); expect(binding('-val | number:4')).toBe('-3,374.3330');
}); });
</doc:scenario>
it('should update', function(){ </doc:example>
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');
});
*/ */
angularFilter.number = function(number, fractionSize){ angularFilter.number = function(number, fractionSize){
if (isNaN(number) || !isFinite(number)) { 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. * @returns {string} Formatted string or the input if input is not recognized as date/millis.
* *
* @example * @example
<span ng:non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: <doc:example>
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/> <doc:source>
<span ng:non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: <span ng:non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>:
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br/> {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br/>
* <span ng:non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>:
* @scenario {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br/>
it('should format date', function(){ </doc:source>
expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). <doc:scenario>
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/); it('should format date', function(){
expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(am|pm)/); 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) { angularFilter.date = function(date, format) {
if (isString(date)) { if (isString(date)) {
@ -255,19 +266,23 @@ angularFilter.date = function(date, format) {
* @css ng-monospace Always applied to the encapsulating element. * @css ng-monospace Always applied to the encapsulating element.
* *
* @example: * @example:
<input type="text" name="objTxt" value="{a:1, b:[]}" <doc:example>
ng:eval="obj = $eval(objTxt)"/> <doc:source>
<pre>{{ obj | json }}</pre> <input type="text" name="objTxt" value="{a:1, b:[]}"
* ng:eval="obj = $eval(objTxt)"/>
* @scenario <pre>{{ obj | json }}</pre>
it('should jsonify filtered objects', function() { </doc:source>
expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}'); <doc:scenario>
}); it('should jsonify filtered objects', function() {
expect(binding('obj | json')).toBe('{\n "a":1,\n "b":[]}');
});
it('should update', function() { it('should update', function() {
input('objTxt').enter('[1, 2, 3]'); input('objTxt').enter('[1, 2, 3]');
expect(binding('obj | json')).toBe('[1,2,3]'); expect(binding('obj | json')).toBe('[1,2,3]');
}); });
</doc:scenario>
</doc:example>
* *
*/ */
angularFilter.json = function(object) { angularFilter.json = function(object) {
@ -324,63 +339,67 @@ angularFilter.uppercase = uppercase;
* @returns {string} Sanitized or raw html. * @returns {string} Sanitized or raw html.
* *
* @example * @example
Snippet: <textarea name="snippet" cols="60" rows="3"> <doc:example>
<doc:source>
Snippet: <textarea name="snippet" cols="60" rows="3">
&lt;p style="color:blue"&gt;an html &lt;p style="color:blue"&gt;an html
&lt;em onmouseover="this.textContent='PWN3D!'"&gt;click here&lt;/em&gt; &lt;em onmouseover="this.textContent='PWN3D!'"&gt;click here&lt;/em&gt;
snippet&lt;/p&gt;</textarea> snippet&lt;/p&gt;</textarea>
<table> <table>
<tr> <tr>
<td>Filter</td> <td>Filter</td>
<td>Source</td> <td>Source</td>
<td>Rendered</td> <td>Rendered</td>
</tr> </tr>
<tr id="html-filter"> <tr id="html-filter">
<td>html filter</td> <td>html filter</td>
<td> <td>
<pre>&lt;div ng:bind="snippet | html"&gt;<br/>&lt;/div&gt;</pre> <pre>&lt;div ng:bind="snippet | html"&gt;<br/>&lt;/div&gt;</pre>
</td> </td>
<td> <td>
<div ng:bind="snippet | html"></div> <div ng:bind="snippet | html"></div>
</td> </td>
</tr> </tr>
<tr id="escaped-html"> <tr id="escaped-html">
<td>no filter</td> <td>no filter</td>
<td><pre>&lt;div ng:bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td> <td><pre>&lt;div ng:bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng:bind="snippet"></div></td> <td><div ng:bind="snippet"></div></td>
</tr> </tr>
<tr id="html-unsafe-filter"> <tr id="html-unsafe-filter">
<td>unsafe html filter</td> <td>unsafe html filter</td>
<td><pre>&lt;div ng:bind="snippet | html:'unsafe'"&gt;<br/>&lt;/div&gt;</pre></td> <td><pre>&lt;div ng:bind="snippet | html:'unsafe'"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng:bind="snippet | html:'unsafe'"></div></td> <td><div ng:bind="snippet | html:'unsafe'"></div></td>
</tr> </tr>
</table> </table>
* </doc:source>
* @scenario <doc:scenario>
it('should sanitize the html snippet ', function(){ it('should sanitize the html snippet ', function(){
expect(using('#html-filter').binding('snippet | html')). expect(using('#html-filter').binding('snippet | html')).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
}); });
it ('should escape snippet without any filter', function() { it('should escape snippet without any filter', function() {
expect(using('#escaped-html').binding('snippet')). expect(using('#escaped-html').binding('snippet')).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" + toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" + "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;"); "snippet&lt;/p&gt;");
}); });
it ('should inline raw snippet if filtered as unsafe', function() { it('should inline raw snippet if filtered as unsafe', function() {
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")). expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).
toBe("<p style=\"color:blue\">an html\n" + toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>"); "snippet</p>");
}); });
it('should update', function(){ it('should update', function(){
input('snippet').enter('new <b>text</b>'); input('snippet').enter('new <b>text</b>');
expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>'); expect(using('#html-filter').binding('snippet | html')).toBe('new <b>text</b>');
expect(using('#escaped-html').binding('snippet')).toBe("new &lt;b&gt;text&lt;/b&gt;"); expect(using('#escaped-html').binding('snippet')).toBe("new &lt;b&gt;text&lt;/b&gt;");
expect(using('#html-unsafe-filter').binding("snippet | html:'unsafe'")).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){ angularFilter.html = function(html, option){
return new HTML(html, option); return new HTML(html, option);
@ -401,59 +420,63 @@ angularFilter.html = function(html, option){
* @returns {string} Html-linkified text. * @returns {string} Html-linkified text.
* *
* @example * @example
Snippet: <textarea name="snippet" cols="60" rows="3"> <doc:example>
Pretty text with some links: <doc:source>
http://angularjs.org/, Snippet: <textarea name="snippet" cols="60" rows="3">
mailto:us@somewhere.org, Pretty text with some links:
another@somewhere.org, http://angularjs.org/,
and one more: ftp://127.0.0.1/.</textarea> mailto:us@somewhere.org,
<table> another@somewhere.org,
<tr> and one more: ftp://127.0.0.1/.</textarea>
<td>Filter</td> <table>
<td>Source</td> <tr>
<td>Rendered</td> <td>Filter</td>
</tr> <td>Source</td>
<tr id="linky-filter"> <td>Rendered</td>
<td>linky filter</td> </tr>
<td> <tr id="linky-filter">
<pre>&lt;div ng:bind="snippet | linky"&gt;<br/>&lt;/div&gt;</pre> <td>linky filter</td>
</td> <td>
<td> <pre>&lt;div ng:bind="snippet | linky"&gt;<br/>&lt;/div&gt;</pre>
<div ng:bind="snippet | linky"></div> </td>
</td> <td>
</tr> <div ng:bind="snippet | linky"></div>
<tr id="escaped-html"> </td>
<td>no filter</td> </tr>
<td><pre>&lt;div ng:bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td> <tr id="escaped-html">
<td><div ng:bind="snippet"></div></td> <td>no filter</td>
</tr> <td><pre>&lt;div ng:bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
</table> <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 not linkify snippet without the linky filter', function() {
it('should linkify the snippet with urls', function(){ expect(using('#escaped-html').binding('snippet')).
expect(using('#linky-filter').binding('snippet | linky')). toBe("Pretty text with some links:\n" +
toBe('Pretty text with some links:\n' + "http://angularjs.org/,\n" +
'<a href="http://angularjs.org/">http://angularjs.org/</a>,\n' + "mailto:us@somewhere.org,\n" +
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>,\n' + "another@somewhere.org,\n" +
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>,\n' + "and one more: ftp://127.0.0.1/.");
'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() { it('should update', function(){
expect(using('#escaped-html').binding('snippet')). input('snippet').enter('new http://link.');
toBe("Pretty text with some links:\n" + expect(using('#linky-filter').binding('snippet | linky')).
"http://angularjs.org/,\n" + toBe('new <a href="http://link">http://link</a>.');
"mailto:us@somewhere.org,\n" + expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
"another@somewhere.org,\n" + });
"and one more: ftp://127.0.0.1/."); </doc:scenario>
}); </doc:example>
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.');
});
*/ */
//TODO: externalize all regexps //TODO: externalize all regexps
angularFilter.linky = function(text){ angularFilter.linky = function(text){

View file

@ -18,17 +18,21 @@ angularFormatter.noop = formatter(identity, identity);
* @returns {?string} A JSON string representation of the model. * @returns {?string} A JSON string representation of the model.
* *
* @example * @example
* <div ng:init="data={name:'misko', project:'angular'}"> <doc:example>
* <input type="text" size='50' name="data" ng:format="json"/> <doc:source>
* <pre>data={{data}}</pre> <div ng:init="data={name:'misko', project:'angular'}">
* </div> <input type="text" size='50' name="data" ng:format="json"/>
* <pre>data={{data}}</pre>
* @scenario </div>
* it('should format json', function(){ </doc:source>
* expect(binding('data')).toEqual('data={\n \"name\":\"misko\",\n \"project\":\"angular\"}'); <doc:scenario>
* input('data').enter('{}'); it('should format json', function(){
* expect(binding('data')).toEqual('data={\n }'); 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){ angularFormatter.json = formatter(toJson, function(value){
return fromJson(value || 'null'); 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`, `[]`. * @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
* *
* @example * @example
* Enter truthy text: <doc:example>
* <input type="text" name="value" ng:format="boolean" value="no"/> <doc:source>
* <input type="checkbox" name="value"/> Enter truthy text:
* <pre>value={{value}}</pre> <input type="text" name="value" ng:format="boolean" value="no"/>
* <input type="checkbox" name="value"/>
* @scenario <pre>value={{value}}</pre>
* it('should format boolean', function(){ </doc:source>
* expect(binding('value')).toEqual('value=false'); <doc:scenario>
* input('value').enter('truthy'); it('should format boolean', function(){
* expect(binding('value')).toEqual('value=true'); 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); angularFormatter['boolean'] = formatter(toString, toBoolean);
@ -70,16 +78,20 @@ angularFormatter['boolean'] = formatter(toString, toBoolean);
* @returns {number} Number from the parsed string. * @returns {number} Number from the parsed string.
* *
* @example * @example
* Enter valid number: <doc:example>
* <input type="text" name="value" ng:format="number" value="1234"/> <doc:source>
* <pre>value={{value}}</pre> Enter valid number:
* <input type="text" name="value" ng:format="number" value="1234"/>
* @scenario <pre>value={{value}}</pre>
* it('should format numbers', function(){ </doc:source>
* expect(binding('value')).toEqual('value=1234'); <doc:scenario>
* input('value').enter('5678'); it('should format numbers', function(){
* expect(binding('value')).toEqual('value=5678'); 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){ angularFormatter.number = formatter(toString, function(obj){
if (obj == _null || NUMBER.exec(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. * @returns {Array} Array parsed from the entered string.
* *
* @example * @example
* Enter a list of items: <doc:example>
* <input type="text" name="value" ng:format="list" value=" chair ,, table"/> <doc:source>
* <input type="text" name="value" ng:format="list"/> Enter a list of items:
* <pre>value={{value}}</pre> <input type="text" name="value" ng:format="list" value=" chair ,, table"/>
* <input type="text" name="value" ng:format="list"/>
* @scenario <pre>value={{value}}</pre>
* it('should format lists', function(){ </doc:source>
* expect(binding('value')).toEqual('value=["chair","table"]'); <doc:scenario>
* this.addFutureAction('change to XYZ', function($window, $document, done){ it('should format lists', function(){
* $document.elements('.doc-example :input:last').val(',,a,b,').trigger('change'); expect(binding('value')).toEqual('value=["chair","table"]');
* done(); this.addFutureAction('change to XYZ', function($window, $document, done){
* }); $document.elements('.doc-example :input:last').val(',,a,b,').trigger('change');
* expect(binding('value')).toEqual('value=["a","b"]'); done();
* }); });
expect(binding('value')).toEqual('value=["a","b"]');
});
</doc:scenario>
</doc:example>
*/ */
angularFormatter.list = formatter( angularFormatter.list = formatter(
function(obj) { return obj ? obj.join(", ") : obj; }, function(obj) { return obj ? obj.join(", ") : obj; },
@ -138,20 +154,24 @@ angularFormatter.list = formatter(
* @returns {String} Trim excess leading and trailing space. * @returns {String} Trim excess leading and trailing space.
* *
* @example * @example
* Enter text with leading/trailing spaces: <doc:example>
* <input type="text" name="value" ng:format="trim" value=" book "/> <doc:source>
* <input type="text" name="value" ng:format="trim"/> Enter text with leading/trailing spaces:
* <pre>value={{value|json}}</pre> <input type="text" name="value" ng:format="trim" value=" book "/>
* <input type="text" name="value" ng:format="trim"/>
* @scenario <pre>value={{value|json}}</pre>
* it('should format trim', function(){ </doc:source>
* expect(binding('value')).toEqual('value="book"'); <doc:scenario>
* this.addFutureAction('change to XYZ', function($window, $document, done){ it('should format trim', function(){
* $document.elements('.doc-example :input:last').val(' text ').trigger('change'); expect(binding('value')).toEqual('value="book"');
* done(); this.addFutureAction('change to XYZ', function($window, $document, done){
* }); $document.elements('.doc-example :input:last').val(' text ').trigger('change');
* expect(binding('value')).toEqual('value="text"'); done();
* }); });
expect(binding('value')).toEqual('value="text"');
});
</doc:scenario>
</doc:example>
*/ */
angularFormatter.trim = formatter( angularFormatter.trim = formatter(
function(obj) { return obj ? trim("" + obj) : ""; } function(obj) { return obj ? trim("" + obj) : ""; }
@ -176,33 +196,36 @@ angularFormatter.trim = formatter(
* @returns {object} object which is located at the selected position. * @returns {object} object which is located at the selected position.
* *
* @example * @example
* <script> <doc:example>
* function DemoCntl(){ <doc:source>
* this.users = [ <script>
* {name:'guest', password:'guest'}, function DemoCntl(){
* {name:'user', password:'123'}, this.users = [
* {name:'admin', password:'abc'} {name:'guest', password:'guest'},
* ]; {name:'user', password:'123'},
* } {name:'admin', password:'abc'}
* </script> ];
* <div ng:controller="DemoCntl"> }
* User: </script>
* <select name="currentUser" ng:format="index:users"> <div ng:controller="DemoCntl">
* <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option> User:
* </select> <select name="currentUser" ng:format="index:users">
* <select name="currentUser" ng:format="index:users"> <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
* <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option> </select>
* </select> <select name="currentUser" ng:format="index:users">
* user={{currentUser.name}}<br/> <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
* password={{currentUser.password}}<br/> </select>
* </div> user={{currentUser.name}}<br/>
* password={{currentUser.password}}<br/>
* @scenario </doc:source>
* it('should retrieve object by index', function(){ <doc:scenario>
* expect(binding('currentUser.password')).toEqual('guest'); it('should retrieve object by index', function(){
* select('currentUser').option('2'); expect(binding('currentUser.password')).toEqual('guest');
* expect(binding('currentUser.password')).toEqual('abc'); select('currentUser').option('2');
* }); expect(binding('currentUser.password')).toEqual('abc');
});
</doc:scenario>
</doc:example>
*/ */
angularFormatter.index = formatter( angularFormatter.index = formatter(
function(object, array){ function(object, array){

View file

@ -22,8 +22,19 @@ function angularServiceInject(name, fn, inject, eager) {
* suffer from window globality. * suffer from window globality.
* *
* @example * @example
<<<<<<< HEAD
<input ng:init="$window = $service('$window'); greeting='Hello World!'" type="text" name="greeting" /> <input ng:init="$window = $service('$window'); greeting='Hello World!'" type="text" name="greeting" />
<button ng:click="$window.alert(greeting)">ALERT</button> <button ng:click="$window.alert(greeting)">ALERT</button>
=======
<doc:example>
<doc:source>
<input ng:init="greeting='Hello World!'" type="text" name="greeting" />
<button ng:click="$window.alert(greeting)">ALERT</button>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
>>>>>>> changed the documentation @example to use <doc:example>
*/ */
angularServiceInject("$window", bind(window, identity, window), [], EAGER); angularServiceInject("$window", bind(window, identity, window), [], EAGER);
@ -63,10 +74,16 @@ angularServiceInject("$document", function(window){
* Notice that using browser's forward/back buttons changes the $location. * Notice that using browser's forward/back buttons changes the $location.
* *
* @example * @example
<a href="#">clear hash</a> | <doc:example>
<a href="#myPath?name=misko">test hash</a><br/> <doc:source>
<input type='text' name="$location.hash"/> <a href="#">clear hash</a> |
<pre>$location = {{$location}}</pre> <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) { angularServiceInject("$location", function($browser) {
var scope = this, var scope = this,
@ -98,9 +115,15 @@ angularServiceInject("$location", function($browser) {
* Browser is updated at the end of $eval() * Browser is updated at the end of $eval()
* *
* @example * @example
* scope.$location.update('http://www.angularjs.org/path#hash?search=x'); <doc:example>
* scope.$location.update({host: 'www.google.com', protocol: 'https'}); <doc:source>
* scope.$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}}); 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 * @param {(string|Object)} href Full href as a string or object with properties
*/ */
@ -133,14 +156,18 @@ angularServiceInject("$location", function($browser) {
* @see update() * @see update()
* *
* @example * @example
* scope.$location.updateHash('/hp') <doc:example>
* ==> update({hashPath: '/hp'}) <doc:source>
* scope.$location.updateHash('/hp')
* scope.$location.updateHash({a: true, b: 'val'}) ==> update({hashPath: '/hp'})
* ==> update({hashSearch: {a: true, b: 'val'}}) scope.$location.updateHash({a: true, b: 'val'})
* ==> update({hashSearch: {a: true, b: 'val'}})
* scope.$location.updateHash('/hp', {a: true}) scope.$location.updateHash('/hp', {a: true})
* ==> update({hashPath: '/hp', hashSearch: {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 {(string|Object)} path A hashPath or hashSearch object
* @param {Object=} search A hashSearch object * @param {Object=} search A hashSearch object
@ -176,7 +203,9 @@ angularServiceInject("$location", function($browser) {
* - everything else * - everything else
* *
* @example * @example
* scope.$location.href = 'http://www.angularjs.org/path#a/b' * <pre>
* scope.$location.href = 'http://www.angularjs.org/path#a/b'
* </pre>
* immediately after this call, other properties are still the old ones... * immediately after this call, other properties are still the old ones...
* *
* This method checks the changes and update location to the consistent state * This method checks the changes and update location to the consistent state
@ -298,13 +327,19 @@ angularServiceInject("$location", function($browser) {
* The main purpose of this service is to simplify debugging and troubleshooting. * The main purpose of this service is to simplify debugging and troubleshooting.
* *
* @example * @example
<p>Reload this page with open console, enter text and hit the log button...</p> <doc:example>
Message: <doc:source>
<input type="text" name="message" value="Hello World!"/> <p>Reload this page with open console, enter text and hit the log button...</p>
<button ng:click="$log.log(message)">log</button> Message:
<button ng:click="$log.warn(message)">warn</button> <input type="text" name="message" value="Hello World!"/>
<button ng:click="$log.info(message)">info</button> <button ng:click="$log.log(message)">log</button>
<button ng:click="$log.error(message)">error</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 var $logFactory; //reference to be used only in tests
angularServiceInject("$log", $logFactory = function($window){ angularServiceInject("$log", $logFactory = function($window){
@ -622,42 +657,46 @@ function switchRouteMatcher(on, when, dstName) {
* widget. * widget.
* *
* @example * @example
<p> This example shows how changing the URL hash causes the <tt>$route</tt>
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.
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.
Try changing the URL in the input box to see changes.
</p>
<script> <doc:example>
angular.service('myApp', function($route) { <doc:source>
$route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl}); <script>
$route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl}); angular.service('myApp', function($route) {
$route.onChange(function() { $route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl});
$route.current.scope.params = $route.current.params; $route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl});
}); $route.onChange(function() {
}, {$inject: ['$route']}); $route.current.scope.params = $route.current.params;
});
}, {$inject: ['$route']});
function BookCntl() { function BookCntl() {
this.name = "BookCntl"; this.name = "BookCntl";
} }
function ChapterCntl() { function ChapterCntl() {
this.name = "ChapterCntl"; this.name = "ChapterCntl";
} }
</script> </script>
Chose: Chose:
<a href="#/Book/Moby">Moby</a> | <a href="#/Book/Moby">Moby</a> |
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> | <a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
<a href="#/Book/Gatsby">Gatsby</a> | <a href="#/Book/Gatsby">Gatsby</a> |
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/> <a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
<input type="text" name="$location.hashPath" size="80" /> <input type="text" name="$location.hashPath" size="80" />
<pre>$location={{$location}}</pre> <pre>$location={{$location}}</pre>
<pre>$route.current.template={{$route.current.template}}</pre> <pre>$route.current.template={{$route.current.template}}</pre>
<pre>$route.current.params={{$route.current.params}}</pre> <pre>$route.current.params={{$route.current.params}}</pre>
<pre>$route.current.scope.name={{$route.current.scope.name}}</pre> <pre>$route.current.scope.name={{$route.current.scope.name}}</pre>
<hr/> <hr/>
<ng:include src="$route.current.template" scope="$route.current.scope"/> <ng:include src="$route.current.template" scope="$route.current.scope"/>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
*/ */
angularServiceInject('$route', function(location) { angularServiceInject('$route', function(location) {
var routes = {}, var routes = {},
@ -1122,43 +1161,49 @@ angularServiceInject('$xhr.cache', function($xhr, $defer, $log){
* @returns {Object} A resource "class". * @returns {Object} A resource "class".
* *
* @example * @example
<script> <doc:example>
function BuzzController($resource) { <doc:source>
this.Activity = $resource( <script>
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments', function BuzzController($resource) {
{alt:'json', callback:'JSON_CALLBACK'}, this.Activity = $resource(
{get:{method:'JSON', params:{visibility:'@self'}}, replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}} '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 = { BuzzController.prototype = {
fetch: function() { fetch: function() {
this.activities = this.Activity.get({userId:this.userId}); this.activities = this.Activity.get({userId:this.userId});
}, },
expandReplies: function(activity) { expandReplies: function(activity) {
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id}); activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
} }
}; };
BuzzController.$inject = ['$resource']; BuzzController.$inject = ['$resource'];
</script> </script>
<div ng:controller="BuzzController"> <div ng:controller="BuzzController">
<input name="userId" value="googlebuzz"/> <input name="userId" value="googlebuzz"/>
<button ng:click="fetch()">fetch</button> <button ng:click="fetch()">fetch</button>
<hr/> <hr/>
<div ng:repeat="item in activities.data.items"> <div ng:repeat="item in activities.data.items">
<h1 style="font-size: 15px;"> <h1 style="font-size: 15px;">
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/> <img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a> <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> <a href ng:click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
</h1> </h1>
{{item.object.content | html}} {{item.object.content | html}}
<div ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;"> <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;"/> <img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}} <a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
</div>
</div>
</div> </div>
</div> </doc:source>
</div> <doc:scenario>
</doc:scenario>
</doc:example>
*/ */
angularServiceInject('$resource', function($xhr){ angularServiceInject('$resource', function($xhr){
var resource = new ResourceFactory($xhr); var resource = new ResourceFactory($xhr);

View file

@ -14,24 +14,27 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* <script> function Cntl(){ <doc:example>
* this.ssnRegExp = /^\d\d\d-\d\d-\d\d\d\d$/; <doc:source>
* } <script> function Cntl(){
* </script> this.ssnRegExp = /^\d\d\d-\d\d-\d\d\d\d$/;
* Enter valid SSN: }
* <div ng:controller="Cntl"> </script>
* <input name="ssn" value="123-45-6789" ng:validate="regexp:ssnRegExp" > Enter valid SSN:
* </div> <div ng:controller="Cntl">
* <input name="ssn" value="123-45-6789" ng:validate="regexp:ssnRegExp" >
* @scenario </div>
* it('should invalidate non ssn', function(){ </doc:source>
* var textBox = element('.doc-example :input'); <doc:scenario>
* expect(textBox.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate non ssn', function(){
* expect(textBox.val()).toEqual('123-45-6789'); var textBox = element('.doc-example :input');
* expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
* input('ssn').enter('123-45-67890'); expect(textBox.val()).toEqual('123-45-6789');
* expect(textBox.attr('className')).toMatch(/ng-validation-error/); input('ssn').enter('123-45-67890');
* }); expect(textBox.attr('className')).toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
* *
*/ */
'regexp': function(value, regexp, msg) { 'regexp': function(value, regexp, msg) {
@ -57,28 +60,29 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* Enter number: <input name="n1" ng:validate="number" > <br> <doc:example>
* Enter number greater than 10: <input name="n2" ng:validate="number:10" > <br> <doc:source>
* Enter number between 100 and 200: <input name="n3" ng:validate="number:100:200" > <br> Enter number: <input name="n1" ng:validate="number" > <br>
* Enter number greater than 10: <input name="n2" ng:validate="number:10" > <br>
* @scenario Enter number between 100 and 200: <input name="n3" ng:validate="number:100:200" > <br>
* it('should invalidate number', function(){ </doc:source>
* var n1 = element('.doc-example :input[name=n1]'); <doc:scenario>
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate number', function(){
* input('n1').enter('1.x'); var n1 = element('.doc-example :input[name=n1]');
* expect(n1.attr('className')).toMatch(/ng-validation-error/); expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('n1').enter('1.x');
* var n2 = element('.doc-example :input[name=n2]'); expect(n1.attr('className')).toMatch(/ng-validation-error/);
* expect(n2.attr('className')).not().toMatch(/ng-validation-error/); var n2 = element('.doc-example :input[name=n2]');
* input('n2').enter('9'); expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
* expect(n2.attr('className')).toMatch(/ng-validation-error/); input('n2').enter('9');
* expect(n2.attr('className')).toMatch(/ng-validation-error/);
* var n3 = element('.doc-example :input[name=n3]'); var n3 = element('.doc-example :input[name=n3]');
* expect(n3.attr('className')).not().toMatch(/ng-validation-error/); expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
* input('n3').enter('201'); input('n3').enter('201');
* expect(n3.attr('className')).toMatch(/ng-validation-error/); expect(n3.attr('className')).toMatch(/ng-validation-error/);
* });
* }); </doc:scenario>
</doc:example>
* *
*/ */
'number': function(value, min, max) { 'number': function(value, min, max) {
@ -110,28 +114,29 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* Enter integer: <input name="n1" ng:validate="integer" > <br> <doc:example>
* Enter integer equal or greater than 10: <input name="n2" ng:validate="integer:10" > <br> <doc:source>
* Enter integer between 100 and 200 (inclusive): <input name="n3" ng:validate="integer:100:200" > <br> Enter integer: <input name="n1" ng:validate="integer" > <br>
* Enter integer equal or greater than 10: <input name="n2" ng:validate="integer:10" > <br>
* @scenario Enter integer between 100 and 200 (inclusive): <input name="n3" ng:validate="integer:100:200" > <br>
* it('should invalidate integer', function(){ </doc:source>
* var n1 = element('.doc-example :input[name=n1]'); <doc:scenario>
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate integer', function(){
* input('n1').enter('1.1'); var n1 = element('.doc-example :input[name=n1]');
* expect(n1.attr('className')).toMatch(/ng-validation-error/); expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* input('n1').enter('1.1');
* var n2 = element('.doc-example :input[name=n2]'); expect(n1.attr('className')).toMatch(/ng-validation-error/);
* expect(n2.attr('className')).not().toMatch(/ng-validation-error/); var n2 = element('.doc-example :input[name=n2]');
* input('n2').enter('10.1'); expect(n2.attr('className')).not().toMatch(/ng-validation-error/);
* expect(n2.attr('className')).toMatch(/ng-validation-error/); input('n2').enter('10.1');
* expect(n2.attr('className')).toMatch(/ng-validation-error/);
* var n3 = element('.doc-example :input[name=n3]'); var n3 = element('.doc-example :input[name=n3]');
* expect(n3.attr('className')).not().toMatch(/ng-validation-error/); expect(n3.attr('className')).not().toMatch(/ng-validation-error/);
* input('n3').enter('100.1'); input('n3').enter('100.1');
* expect(n3.attr('className')).toMatch(/ng-validation-error/); expect(n3.attr('className')).toMatch(/ng-validation-error/);
* });
* }); </doc:scenario>
</doc:example>
*/ */
'integer': function(value, min, max) { 'integer': function(value, min, max) {
var numberError = angularValidator['number'](value, min, max); var numberError = angularValidator['number'](value, min, max);
@ -154,16 +159,20 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* Enter valid date: <doc:example>
* <input name="text" value="1/1/2009" ng:validate="date" > <doc:source>
* Enter valid date:
* @scenario <input name="text" value="1/1/2009" ng:validate="date" >
* it('should invalidate date', function(){ </doc:source>
* var n1 = element('.doc-example :input'); <doc:scenario>
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate date', function(){
* input('text').enter('123/123/123'); var n1 = element('.doc-example :input');
* expect(n1.attr('className')).toMatch(/ng-validation-error/); expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* }); input('text').enter('123/123/123');
expect(n1.attr('className')).toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
* *
*/ */
'date': function(value) { 'date': function(value) {
@ -187,16 +196,20 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* Enter valid email: <doc:example>
* <input name="text" ng:validate="email" value="me@example.com"> <doc:source>
* Enter valid email:
* @scenario <input name="text" ng:validate="email" value="me@example.com">
* it('should invalidate email', function(){ </doc:source>
* var n1 = element('.doc-example :input'); <doc:scenario>
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate email', function(){
* input('text').enter('a@b.c'); var n1 = element('.doc-example :input');
* expect(n1.attr('className')).toMatch(/ng-validation-error/); expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* }); input('text').enter('a@b.c');
expect(n1.attr('className')).toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
* *
*/ */
'email': function(value) { 'email': function(value) {
@ -217,16 +230,20 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* Enter valid phone number: <doc:example>
* <input name="text" value="1(234)567-8901" ng:validate="phone" > <doc:source>
* Enter valid phone number:
* @scenario <input name="text" value="1(234)567-8901" ng:validate="phone" >
* it('should invalidate phone', function(){ </doc:source>
* var n1 = element('.doc-example :input'); <doc:scenario>
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate phone', function(){
* input('text').enter('+12345678'); var n1 = element('.doc-example :input');
* expect(n1.attr('className')).toMatch(/ng-validation-error/); expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* }); input('text').enter('+12345678');
expect(n1.attr('className')).toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
* *
*/ */
'phone': function(value) { 'phone': function(value) {
@ -250,16 +267,20 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* Enter valid phone number: <doc:example>
* <input name="text" value="http://example.com/abc.html" size="40" ng:validate="url" > <doc:source>
* Enter valid phone number:
* @scenario <input name="text" value="http://example.com/abc.html" size="40" ng:validate="url" >
* it('should invalidate url', function(){ </doc:source>
* var n1 = element('.doc-example :input'); <doc:scenario>
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate url', function(){
* input('text').enter('abc://server/path'); var n1 = element('.doc-example :input');
* expect(n1.attr('className')).toMatch(/ng-validation-error/); expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* }); input('text').enter('abc://server/path');
expect(n1.attr('className')).toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
* *
*/ */
'url': function(value) { 'url': function(value) {
@ -280,17 +301,21 @@ extend(angularValidator, {
* @css ng-validation-error * @css ng-validation-error
* *
* @example * @example
* <textarea name="json" cols="60" rows="5" ng:validate="json"> <doc:example>
* {name:'abc'} <doc:source>
* </textarea> <textarea name="json" cols="60" rows="5" ng:validate="json">
* {name:'abc'}
* @scenario </textarea>
* it('should invalidate json', function(){ </doc:source>
* var n1 = element('.doc-example :input'); <doc:scenario>
* expect(n1.attr('className')).not().toMatch(/ng-validation-error/); it('should invalidate json', function(){
* input('json').enter('{name}'); var n1 = element('.doc-example :input');
* expect(n1.attr('className')).toMatch(/ng-validation-error/); expect(n1.attr('className')).not().toMatch(/ng-validation-error/);
* }); input('json').enter('{name}');
expect(n1.attr('className')).toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
* *
*/ */
'json': function(value) { 'json': function(value) {
@ -338,35 +363,35 @@ extend(angularValidator, {
* @css ng-input-indicator-wait, ng-validation-error * @css ng-input-indicator-wait, ng-validation-error
* *
* @example * @example
* <script> <doc:example>
* function MyCntl(){ <doc:source>
* this.myValidator = function (inputToValidate, validationDone) { <script>
* setTimeout(function(){ function MyCntl(){
* validationDone(inputToValidate.length % 2); this.myValidator = function (inputToValidate, validationDone) {
* }, 500); setTimeout(function(){
* } validationDone(inputToValidate.length % 2);
* } }, 500);
* </script> }
* This input is validated asynchronously: }
* <div ng:controller="MyCntl"> </script>
* <input name="text" ng:validate="asynchronous:myValidator"> This input is validated asynchronously:
* </div> <div ng:controller="MyCntl">
* <input name="text" ng:validate="asynchronous:myValidator">
* @scenario </div>
* it('should change color in delayed way', function(){ </doc:source>
* var textBox = element('.doc-example :input'); <doc:scenario>
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/); it('should change color in delayed way', function(){
* expect(textBox.attr('className')).not().toMatch(/ng-validation-error/); var textBox = element('.doc-example :input');
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
* input('text').enter('X'); expect(textBox.attr('className')).not().toMatch(/ng-validation-error/);
* expect(textBox.attr('className')).toMatch(/ng-input-indicator-wait/); input('text').enter('X');
* expect(textBox.attr('className')).toMatch(/ng-input-indicator-wait/);
* pause(.6); pause(.6);
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/);
* expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/); expect(textBox.attr('className')).toMatch(/ng-validation-error/);
* expect(textBox.attr('className')).toMatch(/ng-validation-error/); });
* </doc:scenario>
* }); </doc:example>
* *
*/ */
/* /*

View file

@ -19,117 +19,122 @@
* </select> * </select>
* *
* @example * @example
<table style="font-size:.9em;"> <doc:example>
<tr> <doc:source>
<th>Name</th> <table style="font-size:.9em;">
<th>Format</th> <tr>
<th>HTML</th> <th>Name</th>
<th>UI</th> <th>Format</th>
<th ng:non-bindable>{{input#}}</th> <th>HTML</th>
</tr> <th>UI</th>
<tr> <th ng:non-bindable>{{input#}}</th>
<th>text</th> </tr>
<td>String</td> <tr>
<td><tt>&lt;input type="text" name="input1"&gt;</tt></td> <th>text</th>
<td><input type="text" name="input1" size="4"></td> <td>String</td>
<td><tt>{{input1|json}}</tt></td> <td><tt>&lt;input type="text" name="input1"&gt;</tt></td>
</tr> <td><input type="text" name="input1" size="4"></td>
<tr> <td><tt>{{input1|json}}</tt></td>
<th>textarea</th> </tr>
<td>String</td> <tr>
<td><tt>&lt;textarea name="input2"&gt;&lt;/textarea&gt;</tt></td> <th>textarea</th>
<td><textarea name="input2" cols='6'></textarea></td> <td>String</td>
<td><tt>{{input2|json}}</tt></td> <td><tt>&lt;textarea name="input2"&gt;&lt;/textarea&gt;</tt></td>
</tr> <td><textarea name="input2" cols='6'></textarea></td>
<tr> <td><tt>{{input2|json}}</tt></td>
<th>radio</th> </tr>
<td>String</td> <tr>
<td><tt> <th>radio</th>
&lt;input type="radio" name="input3" value="A"&gt;<br> <td>String</td>
&lt;input type="radio" name="input3" value="B"&gt; <td><tt>
</tt></td> &lt;input type="radio" name="input3" value="A"&gt;<br>
<td> &lt;input type="radio" name="input3" value="B"&gt;
<input type="radio" name="input3" value="A"> </tt></td>
<input type="radio" name="input3" value="B"> <td>
</td> <input type="radio" name="input3" value="A">
<td><tt>{{input3|json}}</tt></td> <input type="radio" name="input3" value="B">
</tr> </td>
<tr> <td><tt>{{input3|json}}</tt></td>
<th>checkbox</th> </tr>
<td>Boolean</td> <tr>
<td><tt>&lt;input type="checkbox" name="input4" value="checked"&gt;</tt></td> <th>checkbox</th>
<td><input type="checkbox" name="input4" value="checked"></td> <td>Boolean</td>
<td><tt>{{input4|json}}</tt></td> <td><tt>&lt;input type="checkbox" name="input4" value="checked"&gt;</tt></td>
</tr> <td><input type="checkbox" name="input4" value="checked"></td>
<tr> <td><tt>{{input4|json}}</tt></td>
<th>pulldown</th> </tr>
<td>String</td> <tr>
<td><tt> <th>pulldown</th>
&lt;select name="input5"&gt;<br> <td>String</td>
&nbsp;&nbsp;&lt;option value="c"&gt;C&lt;/option&gt;<br> <td><tt>
&nbsp;&nbsp;&lt;option value="d"&gt;D&lt;/option&gt;<br> &lt;select name="input5"&gt;<br>
&lt;/select&gt;<br> &nbsp;&nbsp;&lt;option value="c"&gt;C&lt;/option&gt;<br>
</tt></td> &nbsp;&nbsp;&lt;option value="d"&gt;D&lt;/option&gt;<br>
<td> &lt;/select&gt;<br>
<select name="input5"> </tt></td>
<option value="c">C</option> <td>
<option value="d">D</option> <select name="input5">
</select> <option value="c">C</option>
</td> <option value="d">D</option>
<td><tt>{{input5|json}}</tt></td> </select>
</tr> </td>
<tr> <td><tt>{{input5|json}}</tt></td>
<th>multiselect</th> </tr>
<td>Array</td> <tr>
<td><tt> <th>multiselect</th>
&lt;select name="input6" multiple size="4"&gt;<br> <td>Array</td>
&nbsp;&nbsp;&lt;option value="e"&gt;E&lt;/option&gt;<br> <td><tt>
&nbsp;&nbsp;&lt;option value="f"&gt;F&lt;/option&gt;<br> &lt;select name="input6" multiple size="4"&gt;<br>
&lt;/select&gt;<br> &nbsp;&nbsp;&lt;option value="e"&gt;E&lt;/option&gt;<br>
</tt></td> &nbsp;&nbsp;&lt;option value="f"&gt;F&lt;/option&gt;<br>
<td> &lt;/select&gt;<br>
<select name="input6" multiple size="4"> </tt></td>
<option value="e">E</option> <td>
<option value="f">F</option> <select name="input6" multiple size="4">
</select> <option value="e">E</option>
</td> <option value="f">F</option>
<td><tt>{{input6|json}}</tt></td> </select>
</tr> </td>
</table> <td><tt>{{input6|json}}</tt></td>
</tr>
</table>
</doc:source>
<doc:scenario>
* @scenario it('should exercise text', function(){
* it('should exercise text', function(){ input('input1').enter('Carlos');
* input('input1').enter('Carlos'); expect(binding('input1')).toEqual('"Carlos"');
* expect(binding('input1')).toEqual('"Carlos"'); });
* }); it('should exercise textarea', function(){
* it('should exercise textarea', function(){ input('input2').enter('Carlos');
* input('input2').enter('Carlos'); expect(binding('input2')).toEqual('"Carlos"');
* expect(binding('input2')).toEqual('"Carlos"'); });
* }); it('should exercise radio', function(){
* it('should exercise radio', function(){ expect(binding('input3')).toEqual('null');
* expect(binding('input3')).toEqual('null'); input('input3').select('A');
* input('input3').select('A'); expect(binding('input3')).toEqual('"A"');
* expect(binding('input3')).toEqual('"A"'); input('input3').select('B');
* input('input3').select('B'); expect(binding('input3')).toEqual('"B"');
* expect(binding('input3')).toEqual('"B"'); });
* }); it('should exercise checkbox', function(){
* it('should exercise checkbox', function(){ expect(binding('input4')).toEqual('false');
* expect(binding('input4')).toEqual('false'); input('input4').check();
* input('input4').check(); expect(binding('input4')).toEqual('true');
* expect(binding('input4')).toEqual('true'); });
* }); it('should exercise pulldown', function(){
* it('should exercise pulldown', function(){ expect(binding('input5')).toEqual('"c"');
* expect(binding('input5')).toEqual('"c"'); select('input5').option('d');
* select('input5').option('d'); expect(binding('input5')).toEqual('"d"');
* expect(binding('input5')).toEqual('"d"'); });
* }); it('should exercise multiselect', function(){
* it('should exercise multiselect', function(){ expect(binding('input6')).toEqual('[]');
* expect(binding('input6')).toEqual('[]'); select('input6').options('e');
* select('input6').options('e'); expect(binding('input6')).toEqual('["e"]');
* expect(binding('input6')).toEqual('["e"]'); select('input6').options('e', 'f');
* select('input6').options('e', 'f'); expect(binding('input6')).toEqual('["e","f"]');
* expect(binding('input6')).toEqual('["e","f"]'); });
* }); </doc:scenario>
</doc:example>
*/ */
function modelAccessor(scope, element) { function modelAccessor(scope, element) {
@ -193,26 +198,29 @@ function compileFormatter(expr) {
* @element INPUT * @element INPUT
* @css ng-validation-error * @css ng-validation-error
* *
* @exampleDescription * @example
* This example shows how the input element becomes red when it contains invalid input. Correct * This example shows how the input element becomes red when it contains invalid input. Correct
* the input to make the error disappear. * the input to make the error disappear.
* *
* @example <doc:example>
I don't validate: <doc:source>
<input type="text" name="value" value="NotANumber"><br/> I don't validate:
<input type="text" name="value" value="NotANumber"><br/>
I need an integer or nothing: I need an integer or nothing:
<input type="text" name="value" ng:validate="integer"><br/> <input type="text" name="value" ng:validate="integer"><br/>
* </doc:source>
* @scenario <doc:scenario>
it('should check ng:validate', function(){ it('should check ng:validate', function(){
expect(element('.doc-example-live :input:last').attr('className')). expect(element('.doc-example-live :input:last').attr('className')).
toMatch(/ng-validation-error/); toMatch(/ng-validation-error/);
input('value').enter('123'); input('value').enter('123');
expect(element('.doc-example-live :input:last').attr('className')). expect(element('.doc-example-live :input:last').attr('className')).
not().toMatch(/ng-validation-error/); not().toMatch(/ng-validation-error/);
}); });
</doc:scenario>
</doc:example>
*/ */
/** /**
* @workInProgress * @workInProgress
@ -226,19 +234,22 @@ function compileFormatter(expr) {
* @element INPUT * @element INPUT
* @css ng-validation-error * @css ng-validation-error
* *
* @exampleDescription * @example
* This example shows how the input element becomes red when it contains invalid input. Correct * This example shows how the input element becomes red when it contains invalid input. Correct
* the input to make the error disappear. * the input to make the error disappear.
* *
* @example <doc:example>
I cannot be blank: <input type="text" name="value" ng:required><br/> <doc:source>
* I cannot be blank: <input type="text" name="value" ng:required><br/>
* @scenario </doc:source>
it('should check ng:required', function(){ <doc:scenario>
expect(element('.doc-example-live :input').attr('className')).toMatch(/ng-validation-error/); it('should check ng:required', function(){
input('value').enter('123'); expect(element('.doc-example-live :input').attr('className')).toMatch(/ng-validation-error/);
expect(element('.doc-example-live :input').attr('className')).not().toMatch(/ng-validation-error/); input('value').enter('123');
}); expect(element('.doc-example-live :input').attr('className')).not().toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
*/ */
/** /**
* @workInProgress * @workInProgress
@ -256,21 +267,24 @@ function compileFormatter(expr) {
* *
* @element INPUT * @element INPUT
* *
* @exampleDescription * @example
* This example shows how the user input is converted from a string and internally represented as an * This example shows how the user input is converted from a string and internally represented as an
* array. * array.
* *
* @example <doc:example>
Enter a comma separated list of items: <doc:source>
<input type="text" name="list" ng:format="list" value="table, chairs, plate"> Enter a comma separated list of items:
<pre>list={{list}}</pre> <input type="text" name="list" ng:format="list" value="table, chairs, plate">
* <pre>list={{list}}</pre>
* @scenario </doc:source>
it('should check ng:format', function(){ <doc:scenario>
expect(binding('list')).toBe('list=["table","chairs","plate"]'); it('should check ng:format', function(){
input('list').enter(',,, a ,,,'); expect(binding('list')).toBe('list=["table","chairs","plate"]');
expect(binding('list')).toBe('list=["a"]'); input('list').enter(',,, a ,,,');
}); expect(binding('list')).toBe('list=["a"]');
});
</doc:scenario>
</doc:example>
*/ */
function valueAccessor(scope, element) { function valueAccessor(scope, element) {
var validatorName = element.attr('ng:validate') || NOOP, var validatorName = element.attr('ng:validate') || NOOP,
@ -453,28 +467,32 @@ function radioInit(model, view, element) {
* @element INPUT * @element INPUT
* @param {expression} expression to execute. * @param {expression} expression to execute.
* *
* @exampleDescription
* @example * @example
<div ng:init="checkboxCount=0; textCount=0"></div> * @example
<input type="text" name="text" ng:change="textCount = 1 + textCount"> <doc:example>
changeCount {{textCount}}<br/> <doc:source>
<input type="checkbox" name="checkbox" ng:change="checkboxCount = 1 + checkboxCount"> <div ng:init="checkboxCount=0; textCount=0"></div>
changeCount {{checkboxCount}}<br/> <input type="text" name="text" ng:change="textCount = 1 + textCount">
* changeCount {{textCount}}<br/>
* @scenario <input type="checkbox" name="checkbox" ng:change="checkboxCount = 1 + checkboxCount">
it('should check ng:change', function(){ changeCount {{checkboxCount}}<br/>
expect(binding('textCount')).toBe('0'); </doc:source>
expect(binding('checkboxCount')).toBe('0'); <doc:scenario>
it('should check ng:change', function(){
expect(binding('textCount')).toBe('0');
expect(binding('checkboxCount')).toBe('0');
using('.doc-example-live').input('text').enter('abc'); using('.doc-example-live').input('text').enter('abc');
expect(binding('textCount')).toBe('1'); expect(binding('textCount')).toBe('1');
expect(binding('checkboxCount')).toBe('0'); expect(binding('checkboxCount')).toBe('0');
using('.doc-example-live').input('checkbox').check(); using('.doc-example-live').input('checkbox').check();
expect(binding('textCount')).toBe('1'); expect(binding('textCount')).toBe('1');
expect(binding('checkboxCount')).toBe('1'); expect(binding('checkboxCount')).toBe('1');
}); });
</doc:scenario>
</doc:example>
*/ */
function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) { function inputWidget(events, modelAccessor, viewAccessor, initFn, textBox) {
return injectService(['$updateView', '$defer'], function($updateView, $defer, element) { return injectService(['$updateView', '$defer'], function($updateView, $defer, element) {
@ -594,27 +612,31 @@ angularWidget('option', function(){
* @param {string=} onload Expression to evaluate when a new partial is loaded. * @param {string=} onload Expression to evaluate when a new partial is loaded.
* *
* @example * @example
* <select name="url"> <doc:example>
* <option value="angular.filter.date.html">date filter</option> <doc:source>
* <option value="angular.filter.html.html">html filter</option> <select name="url">
* <option value="">(blank)</option> <option value="angular.filter.date.html">date filter</option>
* </select> <option value="angular.filter.html.html">html filter</option>
* <tt>url = <a href="{{url}}">{{url}}</a></tt> <option value="">(blank)</option>
* <hr/> </select>
* <ng:include src="url"></ng:include> <tt>url = <a href="{{url}}">{{url}}</a></tt>
* <hr/>
* @scenario <ng:include src="url"></ng:include>
* it('should load date filter', function(){ </doc:source>
* expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.date/); <doc:scenario>
* }); it('should load date filter', function(){
* it('should change to hmtl filter', function(){ expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.date/);
* select('url').option('angular.filter.html.html'); });
* expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.html/); it('should change to hmtl filter', function(){
* }); select('url').option('angular.filter.html.html');
* it('should change to blank', function(){ expect(element('.doc-example ng\\:include').text()).toMatch(/angular\.filter\.html/);
* select('url').option('(blank)'); });
* expect(element('.doc-example ng\\:include').text()).toEqual(''); it('should change to blank', function(){
* }); select('url').option('(blank)');
expect(element('.doc-example ng\\:include').text()).toEqual('');
});
</doc:scenario>
</doc:example>
*/ */
angularWidget('ng:include', function(element){ angularWidget('ng:include', function(element){
var compiler = this, var compiler = this,
@ -690,32 +712,36 @@ angularWidget('ng:include', function(element){
* * `ng:switch-default`: the default case when no other casses match. * * `ng:switch-default`: the default case when no other casses match.
* *
* @example * @example
<select name="switch"> <doc:example>
<option>settings</option> <doc:source>
<option>home</option> <select name="switch">
<option>other</option> <option>settings</option>
</select> <option>home</option>
<tt>switch={{switch}}</tt> <option>other</option>
</hr> </select>
<ng:switch on="switch" > <tt>switch={{switch}}</tt>
<div ng:switch-when="settings">Settings Div</div> </hr>
<span ng:switch-when="home">Home Span</span> <ng:switch on="switch" >
<span ng:switch-default>default</span> <div ng:switch-when="settings">Settings Div</div>
</ng:switch> <span ng:switch-when="home">Home Span</span>
</code> <span ng:switch-default>default</span>
* </ng:switch>
* @scenario </code>
* it('should start in settings', function(){ </doc:source>
* expect(element('.doc-example ng\\:switch').text()).toEqual('Settings Div'); <doc:scenario>
* }); it('should start in settings', function(){
* it('should change to home', function(){ expect(element('.doc-example ng\\:switch').text()).toEqual('Settings Div');
* select('switch').option('home'); });
* expect(element('.doc-example ng\\:switch').text()).toEqual('Home Span'); it('should change to home', function(){
* }); select('switch').option('home');
* it('should select deafault', function(){ expect(element('.doc-example ng\\:switch').text()).toEqual('Home Span');
* select('switch').option('other'); });
* expect(element('.doc-example ng\\:switch').text()).toEqual('default'); it('should select deafault', function(){
* }); select('switch').option('other');
expect(element('.doc-example ng\\:switch').text()).toEqual('default');
});
</doc:scenario>
</doc:example>
*/ */
var ngSwitch = angularWidget('ng:switch', function (element){ var ngSwitch = angularWidget('ng:switch', function (element){
var compiler = this, var compiler = this,
@ -838,25 +864,29 @@ angularWidget('a', function() {
* *
* For example: `(name, age) in {'adam':10, 'amalie':12}`. * For example: `(name, age) in {'adam':10, 'amalie':12}`.
* *
* @exampleDescription * @example
* This example initializes the scope to a list of names and * This example initializes the scope to a list of names and
* than uses `ng:repeat` to display every person. * than uses `ng:repeat` to display every person.
* @example <doc:example>
<div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]"> <doc:source>
I have {{friends.length}} friends. They are: <div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
<ul> I have {{friends.length}} friends. They are:
<li ng:repeat="friend in friends"> <ul>
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. <li ng:repeat="friend in friends">
</li> [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
</ul> </li>
</div> </ul>
* @scenario </div>
it('should check ng:repeat', function(){ </doc:source>
var r = using('.doc-example-live').repeater('ul li'); <doc:scenario>
expect(r.count()).toBe(2); it('should check ng:repeat', function(){
expect(r.row(0)).toEqual(["1","John","25"]); var r = using('.doc-example-live').repeater('ul li');
expect(r.row(1)).toEqual(["2","Mary","28"]); expect(r.count()).toBe(2);
}); expect(r.row(0)).toEqual(["1","John","25"]);
expect(r.row(1)).toEqual(["2","Mary","28"]);
});
</doc:scenario>
</doc:example>
*/ */
angularWidget("@ng:repeat", function(expression, element){ angularWidget("@ng:repeat", function(expression, element){
element.removeAttr('ng:repeat'); element.removeAttr('ng:repeat');
@ -946,20 +976,24 @@ angularWidget("@ng:repeat", function(expression, element){
* *
* @element ANY * @element ANY
* *
* @exampleDescription * @example
* In this example there are two location where a siple binding (`{{}}`) is present, but the one * In this example there are two location where a siple binding (`{{}}`) is present, but the one
* wrapped in `ng:non-bindable` is left alone. * wrapped in `ng:non-bindable` is left alone.
* *
* @example * @example
<div>Normal: {{1 + 2}}</div> <doc:example>
<div ng:non-bindable>Ignored: {{1 + 2}}</div> <doc:source>
* <div>Normal: {{1 + 2}}</div>
* @scenario <div ng:non-bindable>Ignored: {{1 + 2}}</div>
it('should check ng:non-bindable', function(){ </doc:source>
expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); <doc:scenario>
expect(using('.doc-example-live').element('div:last').text()). it('should check ng:non-bindable', function(){
toMatch(/1 \+ 2/); expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
}); expect(using('.doc-example-live').element('div:last').text()).
toMatch(/1 \+ 2/);
});
</doc:scenario>
</doc:example>
*/ */
angularWidget("@ng:non-bindable", noop); angularWidget("@ng:non-bindable", noop);
@ -989,26 +1023,30 @@ angularWidget("@ng:non-bindable", noop);
* - doesn't require `$route` service to be available on the root scope * - doesn't require `$route` service to be available on the root scope
* *
* *
* # Example * @example
* Because of the nature of this widget, we can't include the usual live example for it. Instead <doc:example>
* following is a code snippet showing the typical usage: <doc:source>
* <script>
<pre> function MyCtrl($route) {
<script> $route.when('/overview', {controller: OverviewCtrl, template: 'guide.overview.html'});
angular.service('routeConfig', function($route) { $route.when('/bootstrap', {controller: BootstrapCtrl, template: 'guide.bootstrap.html'});
$route.when('/foo', {controller: MyCtrl, template: 'foo.html'}); console.log(window.$route = $route);
$route.when('/bar', {controller: MyCtrl, template: 'bar.html'}); };
}, {$inject: ['$route'], $eager: true}); MyCtrl.$inject = ['$route'];
function MyCtrl() {}; function BootstrapCtrl(){}
</script> function OverviewCtrl(){}
<div> </script>
<a href="#/foo">foo</a> | <a href="#/bar">bar</a> | <a href="#/undefined">undefined</a><br/> <div ng:controller="MyCtrl">
The view is included below: <a href="#/overview">overview</a> | <a href="#/bootstrap">bootstrap</a> | <a href="#/undefined">undefined</a><br/>
<hr/> The view is included below:
<ng:view></ng:view> <hr/>
</div> <ng:view></ng:view>
</pre> </div>
</doc:source>
<doc:scenario>
</doc:scenario>
</doc:example>
*/ */
angularWidget('ng:view', function(element) { angularWidget('ng:view', function(element) {
var compiler = this; var compiler = this;