Compare commits

...

45 Commits

Author SHA1 Message Date
Igor Minar 9b53b25f15 cutting the 0.9.4 total-recall release 2010-11-18 22:40:01 -08:00
Igor Minar 3fbfa357ca updated release notes for the 0.9.4 total-recall release 2010-11-18 22:40:00 -08:00
Igor Minar 50ef1f8e35 don't escape $ in hashpath either 2010-11-18 22:40:00 -08:00
Igor Minar 66c0bfaa8e don't escape ! and : in hashPath
This is a temporary fix for Issue #158
2010-11-18 20:51:31 -08:00
Igor Minar 1719b0aca5 fix all closure compilation warnings due to invalid function types 2010-11-18 17:03:43 -08:00
Igor Minar 7ee102eecf add a feedback link to the doc pages 2010-11-18 16:53:11 -08:00
Igor Minar fc7f11d03b add @workInProgress tag and mark all @ngdocs as work in progress 2010-11-18 16:28:42 -08:00
Igor Minar 3c7874b07b don't encode page.name (hash) in docs.js
see discussion in https://github.com/angular/angular.js/pull/158"
2010-11-18 11:56:10 -08:00
Igor Minar 7f339a1782 escape code in ng:bind-attr jsdoc 2010-11-18 11:33:09 -08:00
Igor Minar 72a5f007d8 most of the documentation for angular.scope and friends 2010-11-18 02:35:30 -08:00
Igor Minar 63380bbbda title for index.html should not show raw binding while the app bootstraps 2010-11-18 02:35:30 -08:00
Igor Minar c635b69f5c fix docs and examples for ng:format, ng:required and ng:validate 2010-11-18 02:35:30 -08:00
Igor Minar 522ec1a9ec move attribute widgets to widgets.js file
- move @ng:repeat to widgets.js and its specs to widgetsSpecs.js
- move @ng:non-bindable to widgets.js and its specs to widgetsSpecs.js
- make widget.template suitable for attribute widgets
- fix up the js docs for attribute widgets
2010-11-18 02:35:29 -08:00
Igor Minar 9cb57772a4 fix docs for angular.directive and ng:autobind 2010-11-18 02:35:29 -08:00
Igor Minar d54f09ef29 add spec for incrementing headings 2010-11-18 02:35:29 -08:00
Igor Minar 65989c6f0d add support for {@link} tags within @description and remove implicit linking
use as:
- foo {@link bar}
- foo {@link bar desc}

I'm removing implicit linking because it unintentionally links stuff and
generally interferes with other conversions. We have to link stuff explicitely
from now on.
2010-11-18 02:35:29 -08:00
Igor Minar 4491bbdede docs linkifying regexp should not mess up links alreaded converete by markdown 2010-11-18 02:35:28 -08:00
Igor Minar a6978b201b make @param type and description non-optional 2010-11-18 02:35:28 -08:00
Igor Minar 28e72cbe6b CSS, Parameters, Returns template changes
- make css section optional
- make returns section optional
- change format of the parameters section
- properly format the Returns section
2010-11-18 02:34:55 -08:00
Igor Minar 916dadd8ec adjust spacing of headings in the main doc div 2010-11-18 02:34:54 -08:00
Igor Minar e509ec37f5 fixing angular.lowercase and angular.upppercase jsdocs + api 2010-11-18 02:34:54 -08:00
Igor Minar ee0e9a4452 adding support for @param.optional 2010-11-18 02:34:54 -08:00
Igor Minar 9d36368ff9 fixing angular.filter.number jsdocs 2010-11-18 02:34:54 -08:00
Igor Minar d4bcee0799 toJson and fromJson jsdocs 2010-11-18 02:34:54 -08:00
Igor Minar dd687e2bf5 @returns description should support markdown 2010-11-18 02:34:53 -08:00
Igor Minar 4c69d694d7 make @returns type non-optional 2010-11-18 02:34:53 -08:00
Igor Minar ff7c738c21 fix ng docs for angular, angular.lowercase and uppercase 2010-11-18 02:34:53 -08:00
Igor Minar 51a22cf435 group utility methods/objects while sorting stuff for the side bar 2010-11-18 02:34:53 -08:00
Igor Minar c2c60ab49a remove extra returns tag 2010-11-18 02:34:53 -08:00
Igor Minar 71c2f24fc6 remove extra toolbar directive and support multiple pre's in describe 2010-11-18 02:34:52 -08:00
Igor Minar fc78738cc6 scope docs + lowercase doc fix 2010-11-18 02:34:52 -08:00
Igor Minar c7052f098d add support for @deprecated ng:doc annotation + show warnings in templates 2010-11-18 02:34:31 -08:00
Igor Minar 7d6f5f986e add function.template 2010-11-18 02:34:30 -08:00
Igor Minar beeb5ff908 fix regexp for @param parsing in ng:docs
There is an extra + in the regexp which causes exponential increase in time
needed to parse a @param annotation when the length of default value increases
linearly.
2010-11-16 14:44:35 -08:00
Misko Hevery b2d63ac48b Changed error handling so that better stack traces are displayed in the ng-errors 2010-11-16 14:19:55 -08:00
Igor Minar 4af32de84a docs should use ng:include onload 2010-11-16 13:45:45 -08:00
Igor Minar a130bb899d add onload attribute to ng:include 2010-11-16 11:35:43 -08:00
Vojta Jina cc749760fd Added basic Services, which support @memberOf and @methodOf 2010-11-15 21:55:37 -08:00
Misko Hevery b467a50bc7 generate keyword for searches; improved layout of doc 2010-11-15 12:28:08 -08:00
Misko Hevery a1652057a5 changed to ! notation for indexable apps 2010-11-15 10:04:17 -08:00
Misko Hevery 7e6f999221 added remaining directives and search box. 2010-11-15 10:04:17 -08:00
Igor Minar 625cc7609c fix code name in the change log 2010-11-12 16:18:11 -08:00
Igor Minar c51273b1fb Add test coverage analysis.
- jstd upgrade to head from 2010-11-11
- coverage plugin from the same head as jstd
- test-coverage.sh and server-coverage.sh scripts
- jstd configuration

Generate html by installing lconv (brew or port) and run:

genhtml tmp/lcov/jsTestDriver.conf-coverage.dat

to generate html docs.
2010-11-12 16:13:53 -08:00
Igor Minar 0a8b3161b1 $watch should optionally skip listener exec
- if initRun param is set to false, listener doesn't execute
- the oldValue should equal newValue during the initial execution
- added docs
- added specs
2010-11-11 16:39:01 -08:00
Igor Minar ba554eeb1b preparations for 0.9.4 total-recall iteration 2010-11-11 16:38:23 -08:00
49 changed files with 2863 additions and 599 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;launchConfigurationWorkingSet editPageId=&quot;org.eclipse.ui.resourceWorkingSetPage&quot; factoryID=&quot;org.eclipse.ui.internal.WorkingSetFactory&quot; id=&quot;1262905463390_2&quot; label=&quot;workingSet&quot; name=&quot;workingSet&quot;&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/test&quot; type=&quot;2&quot;/&gt;&#10;&lt;item factoryID=&quot;org.eclipse.ui.internal.model.ResourceFactory&quot; path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/launchConfigurationWorkingSet&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js}/test.sh"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
+1 -1
View File
@@ -2,7 +2,7 @@
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramBuilderLaunchConfigurationType">
<stringAttribute key="org.eclipse.debug.core.ATTR_REFRESH_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/build&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="true"/>
<booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_BUILD_SCOPE" value="${working_set:&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&#10;&lt;resources&gt;&#10;&lt;item path=&quot;/angular.js/docs&quot; type=&quot;2&quot;/&gt;&#10;&lt;item path=&quot;/angular.js/src&quot; type=&quot;2&quot;/&gt;&#10;&lt;/resources&gt;}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/angular.js/gen_docs.sh}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_RUN_BUILD_KINDS" value="full,incremental,auto,"/>
+16
View File
@@ -1,3 +1,19 @@
# <angular/> 0.9.4 total-recall (2010-11-18) #
### Docs
- searchable docs
- UI improvements
- we now have ~85% of the wiki docs migrated to ng docs
- some but not all docs were updated along the way
### Api
- ng:include now supports `onload` attribute (commit cc749760)
### Misc
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
### Docs
+1
View File
@@ -8,6 +8,7 @@
border: 2px solid #FF0000;
font-family: "Courier New", Courier, monospace;
font-size: smaller;
white-space: pre;
}
.ng-validation-error {
+152 -25
View File
@@ -7,9 +7,11 @@ var fs = require('fs'),
Showdown = require('showdown').Showdown;
var documentation = {
section:{},
all:[]
pages:[],
byName: {}
};
var keywordPages = [];
var SRC_DIR = "docs/";
var OUTPUT_DIR = "build/docs/";
@@ -22,26 +24,37 @@ var work = callback.chain(function () {
//console.log('reading', file, '...');
findNgDoc(file, work.waitMany(function(doc) {
parseNgDoc(doc);
if (doc.ngdoc) {
delete doc.raw.text;
var section = documentation.section;
(section[doc.ngdoc] = section[doc.ngdoc] || []).push(doc);
documentation.all.push(doc);
console.log('Found:', doc.ngdoc + ':' + doc.shortName);
mergeTemplate(
doc.ngdoc + '.template',
doc.name + '.html', doc, work.waitFor());
}
processNgDoc(documentation, doc);
}));
}));
}));
}).onError(function(err){
console.log('ERROR:', err.stack || err);
}).onDone(function(){
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain());
keywordPages.sort(function(a,b){
// supper ugly comparator that orders all utility methods and objects before all the other stuff
// like widgets, directives, services, etc.
// Mother of all beatiful code please forgive me for the sin that this code certainly is.
if (a.name === b.name) return 0;
if (a.name === 'angular') return -1;
if (b.name === 'angular') return 1;
function namespacedName(page) {
return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
}
var namespacedA = namespacedName(a),
namespacedB = namespacedName(b);
return namespacedA < namespacedB ? -1 : 1;
});
writeDoc(documentation.pages);
mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(keywordPages)}, callback.chain());
mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain());
copy('docs-scenario.html', callback.chain());
copy('index.html', callback.chain());
copy('docs.css', callback.chain());
mergeTemplate('docs.js', 'docs.js', documentation, callback.chain());
mergeTemplate('doc_widgets.css', 'doc_widgets.css', documentation, callback.chain());
mergeTemplate('doc_widgets.js', 'doc_widgets.js', documentation, callback.chain());
@@ -50,6 +63,24 @@ var work = callback.chain(function () {
if (!this.testmode) work();
////////////////////
function keywords(text){
var keywords = {};
var words = [];
var tokens = text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
tokens.forEach(function(key){
var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/);
if (match){
key = match[1];
if (!keywords[key]) {
keywords[key] = true;
words.push(key);
}
}
});
words.sort();
return words.join(' ');
}
function noop(){}
function mkdirPath(path, callback) {
var parts = path.split(/\//);
@@ -128,12 +159,32 @@ function escapedHtmlTag(doc, name, value) {
function markdownTag(doc, name, value) {
doc[name] = markdown(value.replace(/^#/gm, '##')).
replace(/\<pre\>/gmi, '<pre class="brush: xml; brush: js;" ng:non-bindable>');
replace(/\<pre\>/gmi, '<div ng:non-bindable><pre class="brush: js; html-script: true;">').
replace(/\<\/pre\>/gmi, '</pre></div>');
}
R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m
// 1 123 3 4 42
function markdown(text) {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
return new Showdown.converter().makeHtml(text);
var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/),
match;
parts.forEach(function(text, i){
if (!text.match(/^<pre>/)) {
text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
text = new Showdown.converter().makeHtml(text);
while (match = text.match(R_LINK)) {
text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' +
(match[4] || match[1]) +
'</code></a>');
}
parts[i] = text;
}
});
return parts.join('');
}
function markdownNoP(text) {
@@ -144,6 +195,43 @@ function markdownNoP(text) {
return lines.join('\n');
}
function requiresTag(doc, name, value) {
doc.requires = doc.requires || [];
doc.requires.push({name: value});
}
function propertyTag(doc, name, value) {
doc[name] = doc[name] || [];
var match = value.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
if (match) {
var tag = {
type: match[2],
name: match[3],
description: match[5] || false
};
} else {
throw "[" + doc.raw.file + ":" + doc.raw.line +
"]: @" + name + " must be in format '{type} name description' got: " + value;
}
return doc[name].push(tag);
}
function returnsTag(doc, name, value) {
var match = value.match(/^{(\S+)}\s+(.*)?/);
if (match) {
var tag = {
type: match[1],
description: markdownNoP(match[2]) || false
};
} else {
throw "[" + doc.raw.file + ":" + doc.raw.line +
"]: @" + name + " must be in format '{type} description' got: " + value;
}
return doc[name] = tag;
}
var TAG = {
ngdoc: valueTag,
example: escapedHtmlTag,
@@ -151,29 +239,35 @@ var TAG = {
namespace: valueTag,
css: valueTag,
see: valueTag,
deprecated: valueTag,
workInProgress: function(doc, name, value) {
doc[name] = {description: markdown(value)};
},
usageContent: valueTag,
'function': valueTag,
description: markdownTag,
TODO: markdownTag,
returns: markdownTag,
paramDescription: markdownTag,
exampleDescription: markdownTag,
element: valueTag,
methodOf: valueTag,
name: function(doc, name, value) {
doc.name = value;
var match = value.match(/^angular[\.\#](([^\.]+)\.(.*)|(.*))/);
doc.shortName = match[3] || match[4];
doc.shortName = value.split(/\./).pop();
doc.depth = value.split(/\./).length - 1;
},
param: function(doc, name, value){
doc.param = doc.param || [];
doc.paramRest = doc.paramRest || [];
var match = value.match(/^({([^\s=]+)(=)?}\s*)?(([^\s=]+)|\[(\S+)+=([^\]]+)\])\s+(.*)/);
var match = value.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 12 2 34 4 5 5 6 6 3 7 7
if (match) {
var param = {
type: match[2],
name: match[6] || match[5],
'default':match[7],
description:markdownNoP(value.replace(match[0], match[8]))
type: match[1],
name: match[5] || match[4],
optional: !!match[2],
'default':match[6],
description:markdownNoP(value.replace(match[0], match[7]))
};
doc.param.push(param);
if (!doc.paramFirst) {
@@ -185,7 +279,10 @@ var TAG = {
throw "[" + doc.raw.file + ":" + doc.raw.line +
"]: @param must be in format '{type} name=value description' got: " + value;
}
}
},
property: propertyTag,
requires: requiresTag,
returns: returnsTag
};
function parseNgDoc(doc){
@@ -264,3 +361,33 @@ function findJsFiles(dir, callback){
callback.done();
}));
}
function processNgDoc(documentation, doc) {
if (!doc.ngdoc) return;
console.log('Found:', doc.ngdoc + ':' + doc.name);
documentation.byName[doc.name] = doc;
if (doc.methodOf) {
if (parent = documentation.byName[doc.methodOf]) {
(parent.method = parent.method || []).push(doc);
} else {
throw 'Owner "' + doc.methodOf + '" is not defined.';
}
} else {
documentation.pages.push(doc);
keywordPages.push({
name:doc.name,
type: doc.ngdoc,
keywords:keywords(doc.raw.text)
});
}
}
function writeDoc(pages) {
pages.forEach(function(doc) {
mergeTemplate(
doc.ngdoc + '.template',
doc.name + '.html', doc, callback.chain());
});
}
+23 -2
View File
@@ -1,4 +1,20 @@
<h1>{{name}}</h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
@@ -15,13 +31,18 @@
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
<li><tt>{{name}}</tt>
<tt>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</tt>
<tt>{{#default}}[{{default}}]{{/default}}</tt>
{{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
{{#css}}<h3>CSS</h3>{{/css}}
{{#css}}
<h3>CSS</h3>
{{{css}}}
{{/css}}
{{#example}}
<h2>Example</h2>
-4
View File
@@ -54,10 +54,6 @@
} catch (e) {
alert(e);
}
return function() {
SyntaxHighlighter.highlight();
};
});
function indent(text) {
+1 -1
View File
@@ -1 +1 @@
NG_DOC={{{JSON}}};
NG_PAGES={{{JSON}}};
+3 -3
View File
@@ -1,9 +1,9 @@
{{#all}}
{{#pages}}
describe('{{name}}', function(){
beforeEach(function(){
browser().navigateTo('index.html#{{name}}');
browser().navigateTo('index.html#!{{name}}');
});
// {{raw.file}}:{{raw.line}}
{{{scenario}}}
});
{{/all}}
{{/pages}}
+177
View File
@@ -0,0 +1,177 @@
body {
font-family: Arial, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
}
#page {
display: table-row;
}
#sidebar,
#section {
display: table-cell;
}
a {
color: blue;
}
.nav-section {
margin-left: 1em;
margin-top: 0.5em;
}
.section-title {
float: right;
}
#header {
background-color: #F2C200;
border-bottom: 1px solid #957800;
}
#header h1 {
font-weight: normal;
font-size: 30px;
line-height: 30px;
margin: 0;
padding: 10px 10px;
height: 30px;
}
#header .angular {
font-family: Courier New, monospace;
font-weight: bold;
}
#header h1 a {
color: black;
text-decoration: none;
}
#header h1 a:hover {
text-decoration: underline;
}
#section {
padding: 1em;
width: 100%;
}
#section h1 {
font-family: monospace;
margin-top: 0;
padding-bottom: 5px;
border-bottom: 1px solid #CCC;
}
#section h2 {
margin-top: 1.8em;
}
#section h1 + h2 {
margin-top: 1.3em;
}
#section h3 {
margin-top: 1.5em;
}
#sidebar {
padding: 10px 10px 20px 10px;
background-color: #EEE;
border-right: 1px solid #DDD;
}
#sidebar a {
text-decoration: none;
}
#sidebar a:hover {
text-decoration: underline;
}
#sidebar input {
width: 175px;
margin-bottom: 1em;
}
#sidebar ul {
list-style-type: none;
/*TODO(esprehn): Can we just reset globally and not break examples?*/
margin: 0;
padding: 0;
}
#sidebar ul li {
}
#sidebar ul li a {
display: block;
padding: 2px 2px 2px 4px;
}
#sidebar ul li.selected a {
background-color: #DDD;
border-radius: 5px;
-moz-border-radius: 5px;
border: 1px solid #CCC;
padding: 1px 1px 1px 3px;
}
#sidebar ul li.level-0 {
margin-left: 0em;
font-weight: bold;
font-size: 1.2em;
}
#sidebar ul li.level-1.level-angular {
font-family: monospace;
font-weight: normal;
font-size: 1em;
margin-top: 0;
margin-bottom: 0;
}
#sidebar ul li.level-1 {
margin-left: 1em;
margin-top: 5px;
font-size: 1.1em;
font-weight: bold;
}
#sidebar ul li.level-2 {
margin-left: 2em;
font-family: monospace;
}
#sidebar ul li.level-3 {
margin-left: 3em;
font-family: monospace;
}
.deprecated {
border: 2px solid red;
}
.deprecated legend {
font-weight: bold;
color: red;
}
.workInProgress {
border: 2px solid orange;
}
.workInProgress legend {
font-weight: bold;
color: orange;
}
#feedback {
float: right;
width: 10em;
text-align: right;
}
+46 -5
View File
@@ -1,7 +1,48 @@
function DocController($resource, $location){
this.docs = $resource('documentation.json').get();
this.getPartialDoc = function(){
return encodeURIComponent($location.hashPath) + '.html';
SyntaxHighlighter['defaults'].toolbar = false;
DocsController.$inject = ['$location', '$browser', '$window'];
function DocsController($location, $browser, $window) {
this.pages = NG_PAGES;
window.$root = this.$root;
this.getUrl = function(page){
return '#!' + page.name;
};
this.getCurrentPartial = function(){
return './' + this.getTitle() + '.html';
};
this.getTitle = function(){
var hashPath = $location.hashPath || '!angular';
if (hashPath.match(/^!angular/)) {
this.partialTitle = hashPath.substring(1);
}
return this.partialTitle;
};
this.getClass = function(page) {
var depth = page.name.split(/\./).length - 1,
cssClass = 'level-' + depth + (page.name == this.getTitle() ? ' selected' : '');
if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular';
return cssClass;
};
this.afterPartialLoaded = function() {
$window.scroll(0,0);
SyntaxHighlighter.highlight();
};
this.getFeedbackUrl = function() {
return "mailto:angular@googlegroups.com?" +
"subject=" + escape("Feedback on " + $location.href) + "&" +
"body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
}
}
DocController.$inject=['$resource', '$location'];
angular.filter('short', function(name){
return (name||'').split(/\./).pop();
});
+25 -2
View File
@@ -1,4 +1,20 @@
<h1>{{name}}</h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
@@ -18,15 +34,22 @@ angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/param
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
<li><tt>{{name}}</tt>
<tt>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</tt>
<tt>{{#default}}[{{default}}]{{/default}}</tt>
{{{description}}}</li>
{{/param}}
</ul>
{{#returns}}
<h3>Returns</h3>
{{{returns}}}
<tt>&#123;{{{type}}}&#125;</tt> {{{description}}}
{{/returns}}
{{#css}}
<h3>CSS</h3>
{{{css}}}
{{/css}}
{{#example}}
<h2>Example</h2>
+21 -1
View File
@@ -1,4 +1,20 @@
<h1>{{name}}</h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
@@ -13,11 +29,15 @@ var userInputString = angular.formatter.{{shortName}}.format(modelValue);<br/>
var modelValue = angular.formatter.{{shortName}}.parse(userInputString);
</tt>
{{#returns}}
<h3>Returns</h3>
{{{returns}}}
<tt>&#123;{{{type}}}&#125;</tt> {{{description}}}
{{/returns}}
{{#css}}
<h3>CSS</h3>
{{{css}}}
{{/css}}
{{#example}}
<h2>Example</h2>
+52
View File
@@ -0,0 +1,52 @@
<h1>{{name}}</h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<tt ng:non-bindable>
{{name}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} );
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}</tt>
<tt>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</tt>
<tt>{{#default}}[{{default}}]{{/default}}</tt>
{{{description}}}</li>
{{/param}}
</ul>
{{#returns}}
<h3>Returns</h3>
<tt>&#123;{{{type}}}&#125;</tt> {{{description}}}
{{/returns}}
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
<doc:scenario>{{{scenario}}}</doc:scenario>
</doc:example>
{{/example}}
+28 -130
View File
@@ -1,129 +1,24 @@
<!DOCTYPE HTML>
<html xmlns:ng="http://angularjs.org/" xmlns:doc="http://docs.angularjs.org/" ng:controller="DocsController">
<head>
<title>&lt;Angular/&gt; Docs</title>
<link rel="stylesheet" href="wiki_widgets.css" type="text/css" media="screen">
<link rel="stylesheet" href="doc_widgets.css" type="text/css" media="screen" />
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css" media="screen" />
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css" media="screen" />
<title ng:bind-template="&lt;angular/&gt;: {{getTitle()}}">&lt;angular/&gt;</title>
<meta name="fragment" content="!">
<link rel="stylesheet" href="doc_widgets.css" type="text/css" />
<link rel="stylesheet" href="docs.css" type="text/css"/>
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css"/>
<link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css"/>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript" src="../angular.min.js" ng:autobind></script>
<script type="text/javascript" src="doc_widgets.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js"></script>
<script type="text/javascript" src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js"></script>
<script type="text/javascript" src="../angular.min.js" ng:autobind></script>
<script type="text/javascript" src="docs.js"></script>
<script type="text/javascript" src="doc_widgets.js"></script>
<script type="text/javascript" src="docs-data.js"></script>
<script type="text/javascript">
SyntaxHighlighter['defaults'].toolbar = false;
DocsController.$inject = ['$location', '$browser']
function DocsController($location, $browser) {
this.docs = NG_DOC;
window.$root = this.$root;
this.getUrl = function(page){
return '#' + encodeURIComponent(page.name);
};
this.getCurrentPartial = function(){
if ($location.hashPath.match(/^angular\./)) {
this.partialUrl = './' + $location.hashPath + '.html';
}
return this.partialUrl;
}
this.getTitle = function(){
if ($location.hashPath.match(/^angular\./)) {
this.partialTitle = $location.hashPath;
}
return this.partialTitle;
}
}
</script>
<style type="text/css" media="screen">
body {
font-family: Arial, sans-serif;
font-size: 14px;
margin: 0;
padding: 0;
}
#sidebar {
width: 15em;
float: left;
}
#header {
background-color: #F2C200;
margin-bottom: 1em;
}
#header h1 {
font-weight: normal;
font-size: 30px;
line-height: 30px;
margin: 0;
padding: 10px 10px;
height: 30px;
}
#header .angular {
font-family: Courier New, monospace;
font-weight: bold;
}
#header h1 a {
color: black;
text-decoration: none;
}
#header h1 a:hover {
text-decoration: underline;
}
#section {
position: absolute;
z-index: -1;
width: 100%;
}
#section ng\:include {
margin: 0 1em 1em 15em;
display: block;
}
#section h1 {
font-family: monospace;
margin-top: 0;
}
#sidebar h2 {
font-size: 1.2em;
margin: 5px 5px 5px 0.8em;
}
#sidebar ul {
list-style-type: none;
/*TODO(esprehn): Can we just reset globally and not break examples?*/
margin: 0;
padding: 0;
}
#sidebar ul li {
margin: 0;
padding: 1px 1px 1px 1.5em;
}
.nav-section {
margin-left: 1em;
margin-top: 0.5em;
}
.section-title {
float: right;
}
</style>
<title>&lt;angular/&gt;: {{getTitle()}}</title>
</head>
<body>
<div id="header">
@@ -132,18 +27,21 @@
<a href="index.html"><span class="angular">&lt;angular/&gt;</span> Docs</a>
</h1>
</div>
<div id="sidebar" class="nav">
<div ng:repeat="(name, type) in docs.section" class="nav-section">
<h2>{{name}}</h2>
<ul>
<li ng:repeat="page in type.$orderBy('shortName')">
<a href="{{getUrl(page)}}" ng:click="">{{page.shortName}}</a>
</li>
</ul>
</div>
</div>
<div id="section">
<ng:include src="getCurrentPartial()"></ng:include>
<div id="page">
<div id="sidebar" class="nav">
<div class="doc-list">
<input type="text" name="filterText" placeholder="search documentaiton"/>
<ul>
<li ng:repeat="page in pages.$filter(filterText)" ng:class="getClass(page)">
<a href="{{getUrl(page)}}" ng:click="">{{page.name | short}}</a>
</li>
</ul>
</div>
</div>
<div id="section">
<a id="feedback" ng:href="{{getFeedbackUrl()}}">Report an Issue or Ask a Question</a>
<ng:include src="getCurrentPartial()" onload="afterPartialLoaded()"></ng:include>
</div>
</div>
</body>
</html>
+16
View File
@@ -1,4 +1,20 @@
<h1>{{name}}</h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
{{{description}}}
{{#example}}
+53
View File
@@ -0,0 +1,53 @@
<h1><tt>{{name}}</tt></h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
<h2>Dependencies</h2>
<ul>
{{#requires}}
<li><tt>{{name}}</tt></li>
{{/requires}}
</ul>
<h2>Methods</h2>
<ul>
{{#method}}
<li><tt>{{shortName}}</tt>: {{{description}}}</li>
{{/method}}
</ul>
<h2>Properties</h2>
<ul>
{{#property}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}</tt>{{#description}}: {{{description}}}{{/description}}</li>
{{/property}}
</ul>
{{#example}}
<h2>Example</h2>
{{{exampleDescription}}}
<doc:example>
<doc:source>
{{/example}}
{{{example}}}
{{#example}}
</doc:source>
{{#scenario}}<doc:scenario>{{{scenario}}}</doc:scenario>{{/scenario}}
</doc:example>
{{/example}}
+207 -13
View File
@@ -11,6 +11,63 @@ describe('collect', function(){
expect(collect.markdown('<angular/>')).
toEqual('<p><tt>&lt;angular/&gt;</tt></p>');
});
it('should not replace anything in <pre>', function(){
expect(collect.markdown('bah x\n<pre>\nangular.k\n</pre>\n asdf x')).
toEqual(
'<p>bah x</p>' +
'<pre>\nangular.k\n</pre>' +
'<p>asdf x</p>');
});
});
describe('processNgDoc', function() {
var processNgDoc = collect.processNgDoc,
documentation;
beforeEach(function() {
documentation = {
pages: [],
byName: {}
};
});
it('should store references to docs by name', function() {
var doc = {ngdoc: 'section', name: 'fake', raw: {text:''}};
processNgDoc(documentation, doc);
expect(documentation.byName.fake).toBe(doc);
});
it('should connect doc to owner (specified by @methodOf)', function() {
var parentDoc = {ngdoc: 'section', name: 'parent', raw: {text:''}};
var doc = {ngdoc: 'section', name: 'child', methodOf: 'parent', raw: {text:''}};
processNgDoc(documentation, parentDoc);
processNgDoc(documentation, doc);
expect(documentation.byName.parent.method).toBeDefined();
expect(documentation.byName.parent.method[0]).toBe(doc);
});
it('should not add doc to sections if @memberOf specified', function() {
var parentDoc = {ngdoc: 'parent', name: 'parent', raw: {text:''}};
var doc = {ngdoc: 'child', name: 'child', methodOf: 'parent', raw: {text:''}};
processNgDoc(documentation, parentDoc);
processNgDoc(documentation, doc);
expect(documentation.pages.child).not.toBeDefined();
});
it('should throw exception if owner does not exist', function() {
expect(function() {
processNgDoc(documentation, {ngdoc: 'section', methodOf: 'not.exist', raw: {text:''}});
}).toThrow('Owner "not.exist" is not defined.');
});
it('should ignore non-ng docs', function() {
var doc = {name: 'anything'};
expect(function() {
processNgDoc(documentation, doc);
}).not.toThrow();
expect(documentation.pages).not.toContain(doc);
});
});
describe('TAG', function(){
@@ -26,33 +83,162 @@ describe('collect', function(){
'{(number|string)} number Number \n to format.');
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'number',
name : 'number',
optional: false,
'default' : undefined,
description : 'Number \n to format.' }]);
});
it('should parse with default', function(){
it('should parse with default and optional', function(){
TAG.param(doc, 'param',
'{(number|string)=} [fractionSize=2] desc');
expect(doc.param).toEqual([{
type : '(number|string)',
name : 'fractionSize',
name : 'fractionSize',
optional: true,
'default' : '2',
description : 'desc' }]);
});
});
describe('@describe', function(){
it('should support pre blocks', function(){
TAG.description(doc, 'description', '<pre class="brush: xml;" ng:non-bindable>abc</pre>');
expect(doc.description).toEqual('<pre class="brush: xml;" ng:non-bindable>abc</pre>');
describe('@requires', function() {
it('should parse more @requires tag into array', function() {
TAG.requires(doc, 'requires', '$service');
TAG.requires(doc, 'requires', '$another');
expect(doc.requires).toEqual([
{name: '$service'},
{name: '$another'}
]);
});
});
describe('@property', function() {
it('should parse @property tags into array', function() {
TAG.property(doc, 'property', '{type} name1 desc');
TAG.property(doc, 'property', '{type} name2 desc');
expect(doc.property.length).toEqual(2);
});
describe('@example', function(){
it('should not remove {{}}', function(){
TAG.example(doc, 'example', 'text {{ abc }}');
expect(doc.example).toEqual('text {{ abc }}');
});
it('should parse @property with only name', function() {
TAG.property(doc, 'property', 'fake');
expect(doc.property[0].name).toEqual('fake');
});
it('should parse @property with optional type', function() {
TAG.property(doc, 'property', '{string} name');
expect(doc.property[0].name).toEqual('name');
expect(doc.property[0].type).toEqual('string');
});
it('should parse @property with optional description', function() {
TAG.property(doc, 'property', 'name desc rip tion');
expect(doc.property[0].name).toEqual('name');
expect(doc.property[0].description).toEqual('desc rip tion');
});
it('should parse @property with type and description both', function() {
TAG.property(doc, 'property', '{bool} name desc rip tion');
expect(doc.property[0].name).toEqual('name');
expect(doc.property[0].type).toEqual('bool');
expect(doc.property[0].description).toEqual('desc rip tion');
});
/**
* If property description is undefined, this variable is not set in the template,
* so the whole @description tag is used instead
*/
it('should set undefined description to "false"', function() {
TAG.property(doc, 'property', 'name');
expect(doc.property[0].description).toBe(false);
});
});
describe('@methodOf', function() {
it('should parse @methodOf tag', function() {
expect(function() {
TAG.methodOf(doc, 'methodOf', 'parentName');
}).not.toThrow();
expect(doc.methodOf).toEqual('parentName');
});
});
describe('@returns', function() {
it('should not parse @returns without type', function() {
expect(function() {TAG.returns(doc, 'returns', 'lala');})
.toThrow();
});
it('should parse @returns with type and description', function() {
TAG.returns(doc, 'returns', '{string} descrip tion');
expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'});
});
it('should transform description of @returns with markdown', function() {
TAG.returns(doc, 'returns', '{string} descrip *tion*');
expect(doc.returns).toEqual({type: 'string', description: 'descrip <em>tion</em>'});
});
});
describe('@description', function(){
it('should support pre blocks', function(){
TAG.description(doc, 'description', '<pre>abc</pre>');
expect(doc.description).
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>');
});
it('should support multiple pre blocks', function() {
TAG.description(doc, 'description', 'foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>');
expect(doc.description).
toBe('<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' +
'<h2>bah</h2>\n\n' +
'<p>foo </p>' +
'<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>');
});
it('should support nested @link annotations with or without description', function() {
TAG.description(doc, 'description',
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
'dad{@link angular.foo}\n\n' +
'{@link angular.directive.ng:foo ng:foo}');
expect(doc.description).
toBe('<p>foo <a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
'<p>da <a href="#!angular.foo"><code>bar foo bar</code></a> </p>\n\n' +
'<p>dad<a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' +
'<p><a href="#!angular.directive.ng:foo"><code>ng:foo</code></a></p>');
});
it('should increment all headings by one', function() {
TAG.description(doc, 'description', '# foo\nabc');
expect(doc.description).
toBe('<h2>foo</h2>\n\n<p>abc</p>');
});
});
describe('@example', function(){
it('should not remove {{}}', function(){
TAG.example(doc, 'example', 'text {{ abc }}');
expect(doc.example).toEqual('text {{ abc }}');
});
});
describe('@deprecated', function() {
it('should parse @deprecated', function() {
TAG.deprecated(doc, 'deprecated', 'Replaced with foo.');
expect(doc.deprecated).toBe('Replaced with foo.');
})
});
describe('@workInProgress', function() {
it('should parse @workInProgress without a description and default to true', function() {
TAG.workInProgress(doc, 'workInProgress', '');
expect(doc.workInProgress).toEqual({description: ''});
});
it('should parse @workInProgress with a description', function() {
TAG.workInProgress(doc, 'workInProgress', 'my description');
expect(doc.workInProgress).toEqual({description: '<p>my description</p>'});
});
});
@@ -69,6 +255,14 @@ describe('collect', function(){
});
});
describe('keywords', function(){
var keywords = collect.keywords;
it('should collect keywords', function(){
expect(keywords('\nHello: World! @ignore.')).toEqual('hello world');
expect(keywords('The `ng:class-odd` and ')).toEqual('and ng:class-odd the');
});
});
});
function load(path){
+1 -1
View File
@@ -1,6 +1,6 @@
require.paths.push("./lib");
var jasmine = require('jasmine-1.0.1');
var sys = require('sys');
var sys = require('util');
for(var key in jasmine) {
global[key] = jasmine[key];
+21 -1
View File
@@ -1,5 +1,20 @@
<h1>{{name}}</h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
@@ -17,13 +32,18 @@ angular.validator.{{shortName}}({{paramFirst.name}}{{#paramRest}}{{^default}}, {
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
<li><tt>{{name}}</tt>
<tt>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</tt>
<tt>{{#default}}[{{default}}]{{/default}}</tt>
{{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
{{#css}}
<h3>CSS</h3>
{{{css}}}
{{/css}}
{{#example}}
<h2>Example</h2>
+30 -1
View File
@@ -1,29 +1,58 @@
<h1>{{name}}</h1>
{{#workInProgress}}
<fieldset class="workInProgress">
<legend>Work In Progress</legend>
This page is currently being revised. It might be incomplete or contain inaccuracies.
{{{workInProgress.description}}}
</fieldset>
{{/workInProgress}}
{{#deprecated}}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{{deprecated}}
</fieldset>
{{/deprecated}}
<h2>Description</h2>
{{{description}}}
<h2>Usage</h2>
<h3>In HTML Template Binding</h3>
<tt>
{{^element}}
<pre>
&lt;{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}&gt;{{#usageContent}}
{{usageContent}}
{{/usageContent}}&lt;/{{shortName}}&gt;
</pre>
{{/element}}
{{#element}}
<pre>
&lt;{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}"&gt;
...
&lt;/{{element}}&gt;
</pre>
{{/element}}
</tt>
<h3>Parameters</h3>
<ul>
{{#param}}
<li><tt>{{name}}:{{#type}}{{type}}{{/type}}{{^type}}:*{{/type}}{{#default}}={{default}}{{/default}}</tt>: {{{description}}}</li>
<li><tt>{{name}}</tt>
<tt>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</tt>
<tt>{{#default}}[{{default}}]{{/default}}</tt>
{{{description}}}</li>
{{/param}}
</ul>
{{{paramDescription}}}
{{#css}}
<h3>CSS</h3>
{{{css}}}
{{/css}}
{{#example}}
<h2>Example</h2>
+34
View File
@@ -0,0 +1,34 @@
server: http://localhost:9876
load:
- lib/jasmine-1.0.1/jasmine.js
- lib/jasmine-jstd-adapter/JasmineAdapter.js
- lib/jquery/jquery-1.4.2.js
- test/jquery_remove.js
- src/Angular.js
- src/JSON.js
- src/*.js
- example/personalLog/*.js
- test/testabilityPatch.js
- src/scenario/Scenario.js
- src/scenario/output/*.js
- src/scenario/*.js
- test/angular-mocks.js
- test/scenario/*.js
- test/scenario/output/*.js
- test/*.js
- example/personalLog/test/*.js
exclude:
- test/jquery_alias.js
- src/angular.prefix
- src/angular.suffix
- src/angular-bootstrap.js
- src/scenario/angular-bootstrap.js
- src/AngularPublic.js
plugin:
- name: "coverage"
jar: "lib/jstestdriver/coverage.jar"
module: "com.google.jstestdriver.coverage.CoverageModule"
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
java -jar lib/jstestdriver/JsTestDriver.jar --port 9876 --browserTimeout 20000 --config jsTestDriver-coverage.conf
+286 -11
View File
@@ -4,27 +4,29 @@ if (typeof document.getAttribute == $undefined)
document.getAttribute = function() {};
/**
* @ngdoc
* @workInProgress
* @ngdoc function
* @name angular.lowercase
* @function
*
* @description Converts string to lowercase
* @param {string} value
* @param {string} string String to be lowercased.
* @returns {string} Lowercased string.
*/
var lowercase = function (value){ return isString(value) ? value.toLowerCase() : value; };
var lowercase = function (string){ return isString(string) ? string.toLowerCase() : string; };
/**
* @ngdoc
* @workInProgress
* @ngdoc function
* @name angular.uppercase
* @function
*
* @description Converts string to uppercase.
* @param {string} value
* @param {string} string String to be uppercased.
* @returns {string} Uppercased string.
*/
var uppercase = function (value){ return isString(value) ? value.toUpperCase() : value; };
var uppercase = function (string){ return isString(string) ? string.toUpperCase() : string; };
var manualLowercase = function (s) {
@@ -75,6 +77,7 @@ var _undefined = undefined,
PRIORITY_WATCH = -1000,
PRIORITY_LAST = 99999,
PRIORITY = {'FIRST': PRIORITY_FIRST, 'LAST': PRIORITY_LAST, 'WATCH':PRIORITY_WATCH},
Error = window.Error,
jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy
_ = window['_'],
/** holds major version number for IE or NaN for real browsers */
@@ -85,15 +88,73 @@ var _undefined = undefined,
error = window[$console] ? bind(window[$console], window[$console]['error'] || noop) : noop,
/**
* @workInProgress
* @ngdoc overview
* @name angular
* @namespace The exported angular namespace.
*/
angular = window[$angular] || (window[$angular] = {}),
angularTextMarkup = extensionMap(angular, 'markup'),
angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.directive
* @namespace Namespace for all directives.
*
* @description
* A directive is an HTML attribute that you can use in an existing HTML element type or in a
* DOM element type that you create as {@link angular.widget}, to modify that element's
* properties. You can use any number of directives per element.
*
* For example, you can add the ng:bind directive as an attribute of an HTML span element, as in
* `<span ng:bind="1+2"></span>`. How does this work? The compiler passes the attribute value
* `1+2` to the ng:bind extension, which in turn tells the {@link angular.scope} to watch that
* expression and report changes. On any change it sets the span text to the expression value.
*
* Here's how to define {@link angular.directive.ng:bind ng:bind}:
* <pre>
angular.directive('ng:bind', function(expression, compiledElement) {
var compiler = this;
return function(linkElement) {
var currentScope = this;
currentScope.$watch(expression, function(value) {
linkElement.text(value);
});
};
});
* </pre>
*
* # Directive vs. Attribute Widget
* Both [attribute widgets](#!angular.widget) and directives can compile a DOM element
* attribute. So why have two different ways to do the same thing? The answer is that order
* matters, but we have no control over the order in which attributes are read. To solve this
* we apply attribute widget before the directive.
*
* For example, consider this piece of HTML, which uses the directives `ng:repeat`, `ng:init`,
* and `ng:bind`:
* <pre>
<ul ng:init="people=['mike', 'mary']">
<li ng:repeat="person in people" ng:init="a=a+1" ng:bind="person"></li>
</ul>
* </pre>
*
* Notice that the order of execution matters here. We need to execute
* {@link angular.directive.ng:repeat ng:repeat} before we run the
* {@link angular.directive.ng:init ng:init} and `ng:bind` on the `<li/>;`. This is because we
* want to run the `ng:init="a=a+1` and `ng:bind="person"` once for each person in people. We
* could not have used directive to create this template because attributes are read in an
* unspecified order and there is no way of guaranteeing that the repeater attribute would
* execute first. Using the `ng:repeat` attribute directive ensures that we can transform the
* DOM element into a template.
*
* Widgets run before directives. Widgets may manipulate the DOM whereas directives are not
* expected to do so, and so they run last.
*/
angularDirective = extensionMap(angular, 'directive'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.widget
* @namespace Namespace for all widgets.
@@ -169,6 +230,7 @@ var _undefined = undefined,
angularWidget = extensionMap(angular, 'widget', lowercase),
/**
* @workInProgress
* @ngdoc overview
* @name angular.validator
* @namespace Namespace for all filters.
@@ -245,8 +307,8 @@ var _undefined = undefined,
*/
angularValidator = extensionMap(angular, 'validator'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.filter
* @namespace Namespace for all filters.
@@ -262,8 +324,9 @@ var _undefined = undefined,
* # Standard Filters
*
* The Angular framework provides a standard set of filters for common operations, including:
* {@link angular.filter.currency}, {@link angular.filter.json}, {@link angular.filter.number},
* and {@link angular.filter.html}. You can also add your own filters.
* {@link angular.filter.currency currency}, {@link angular.filter.json json},
* {@link angular.filter.number number}, and {@link angular.filter.html html}. You can also add
* your own filters.
*
*
* # Syntax
@@ -324,6 +387,7 @@ var _undefined = undefined,
*/
angularFilter = extensionMap(angular, 'filter'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.formatter
* @namespace Namespace for all formats.
@@ -332,7 +396,7 @@ var _undefined = undefined,
* The formatters are responsible for translating user readable text in an input widget to a
* data model stored in an application.
*
* # Writting your own Fromatter
* # Writting your own Formatter
* Writing your own formatter is easy. Just register a pair of JavaScript functions with
* `angular.formatter`. One function for parsing user input text to the stored form,
* and one for formatting the stored data to user-visible text.
@@ -399,10 +463,58 @@ var _undefined = undefined,
* $document.elements('.doc-example input:last').val('XYZ').trigger('change');
* done();
* });
* expect(element('input:first').val()).toEqual('zyx');
* expect(element('.doc-example input:first').val()).toEqual('zyx');
* });
*/
angularFormatter = extensionMap(angular, 'formatter'),
/**
* @workInProgress
* @ngdoc overview
* @name angular.service
*
* @description
* # Overview
* Services are substituable objects, which are wired together using dependency injection.
* Each service could have dependencies (other services), which are passed in constructor.
* Because JS is dynamicaly typed language, dependency injection can not use static types
* to satisfy these dependencies, so each service must explicitely define its dependencies.
* This is done by $inject property.
*
* For now, life time of all services is the same as the life time of page.
*
*
* # Standard services
* The Angular framework provides a standard set of services for common operations.
* You can write your own services and rewrite these standard services as well.
* Like other core angular variables, standard services always start with $.
*
* * `angular.service.$window`
* * `angular.service.$document`
* * `angular.service.$location`
* * `angular.service.$log`
* * `angular.service.$exceptionHandler`
* * `angular.service.$hover`
* * `angular.service.$invalidWidgets`
* * `angular.service.$route`
* * `angular.service.$xhr`
* * `angular.service.$xhr.error`
* * `angular.service.$xhr.bulk`
* * `angular.service.$xhr.cache`
* * `angular.service.$resource`
* * `angular.service.$cookies`
* * `angular.service.$cookieStore`
*
* # Writing your own services
* <pre>
* angular.service('notify', function(location) {
* this.one = function() {
* }
* }, {$inject: ['$location']});
* </pre>
*
* # Using services in controller
*/
angularService = extensionMap(angular, 'service'),
angularCallbacks = extensionMap(angular, 'callbacks'),
nodeName,
@@ -441,6 +553,18 @@ function foreachSorted(obj, iterator, context) {
}
function formatError(arg) {
if (arg instanceof Error) {
if (arg.stack) {
arg = arg.stack;
} else if (arg.sourceURL) {
arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
}
}
return arg;
}
function extend(dst) {
foreach(arguments, function(obj){
if (obj !== dst) {
@@ -767,6 +891,157 @@ function toKeyValue(obj) {
return parts.length ? parts.join('&') : '';
}
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:autobind
* @element script
*
* @TODO ng:autobind is not a directive!! it should be documented as bootstrap parameter in a
* separate bootstrap section.
* @TODO rename to ng:autobind to ng:autoboot
*
* @description
* This section explains how to bootstrap your application with angular using either the angular
* javascript file.
*
*
* ## The angular distribution
* Note that there are two versions of the angular javascript file that you can use:
*
* * `angular.js` - the development version - this file is unobfuscated, uncompressed, and thus
* human-readable and useful when developing your angular applications.
* * `angular.min.js` - the production version - this is a minified and obfuscated version of
* `angular.js`. You want to use this version when you want to load a smaller but functionally
* equivalent version of the code in your application. We use the Closure compiler to create this
* file.
*
*
* ## Auto-bootstrap with `ng:autobind`
* The simplest way to get an <angular/> application up and running is by inserting a script tag in
* your HTML file that bootstraps the `http://code.angularjs.org/angular-x.x.x.min.js` code and uses
* the special `ng:autobind` attribute, like in this snippet of HTML:
*
* <pre>
&lt;!doctype html&gt;
&lt;html xmlns:ng="http://angularjs.org"&gt;
&lt;head&gt;
&lt;script type="text/javascript" src="http://code.angularjs.org/angular-0.9.3.min.js"
ng:autobind&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
Hello {{'world'}}!
&lt;/body&gt;
&lt;/html&gt;
* </pre>
*
* The `ng:autobind` attribute tells <angular/> to compile and manage the whole HTML document. The
* compilation occurs in the page's `onLoad` handler. Note that you don't need to explicitly add an
* `onLoad` event; auto bind mode takes care of all the magic for you.
*
*
* ## Auto-bootstrap with `#autobind`
* In rare cases when you can't define the `ng` namespace before the script tag (e.g. in some CMS
* systems, etc), it is possible to auto-bootstrap angular by appending `#autobind` to the script
* src URL, like in this snippet:
*
* <pre>
&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;script type="text/javascript"
src="http://code.angularjs.org/angular-0.9.3.min.js#autobind"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div xmlns:ng="http://angularjs.org"&gt;
Hello {{'world'}}!
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
* </pre>
*
* In this case it's the `#autobind` URL fragment that tells angular to auto-bootstrap.
*
*
* ## Filename Restrictions for Auto-bootstrap
* In order for us to find the auto-bootstrap script attribute or URL fragment, the value of the
* `script` `src` attribute that loads angular script must match one of these naming
* conventions:
*
* - `angular.js`
* - `angular-min.js`
* - `angular-x.x.x.js`
* - `angular-x.x.x.min.js`
* - `angular-x.x.x-xxxxxxxx.js` (dev snapshot)
* - `angular-x.x.x-xxxxxxxx.min.js` (dev snapshot)
* - `angular-bootstrap.js` (used for development of angular)
*
* Optionally, any of the filename format above can be prepended with relative or absolute URL that
* ends with `/`.
*
*
* ## Manual Bootstrap
* Using auto-bootstrap is a handy way to start using <angular/>, but advanced users who want more
* control over the initialization process might prefer to use manual bootstrap instead.
*
* The best way to get started with manual bootstraping is to look at the magic behind `ng:autobind`
* by writing out each step of the autobind process explicitly. Note that the following code is
* equivalent to the code in the previous section.
*
* <pre>
&lt;!doctype html&gt;
&lt;html xmlns:ng="http://angularjs.org"&gt;
&lt;head&gt;
&lt;script type="text/javascript" src="http://code.angularjs.org/angular-0.9.3.min.js"
ng:autobind&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
(function(window, previousOnLoad){
window.onload = function(){
try { (previousOnLoad||angular.noop)(); } catch(e) {}
angular.compile(window.document).$init();
};
})(window, window.onload);
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
Hello {{'World'}}!
&lt;/body&gt;
&lt;/html&gt;
* </pre>
*
* This is the sequence that your code should follow if you're bootstrapping angular on your own:
*
* * After the page is loaded, find the root of the HTML template, which is typically the root of
* the document.
* * Run the HTML compiler, which converts the templates into an executable, bi-directionally bound
* application.
*
*
* ##XML Namespace
* *IMPORTANT:* When using <angular/> you must declare the ng namespace using the xmlns tag. If you
* don't declare the namespace, Internet Explorer does not render widgets properly.
*
* <pre>
* &lt;html xmlns:ng="http://angularjs.org"&gt;
* </pre>
*
*
* ## Create your own namespace
* If you want to define your own widgets, you must create your own namespace and use that namespace
* to form the fully qualified widget name. For example, you could map the alias `my` to your domain
* and create a widget called my:widget. To create your own namespace, simply add another xmlsn tag
* to your page, create an alias, and set it to your unique domain:
*
* <pre>
* &lt;html xmlns:ng="http://angularjs.org" xmlns:my="http://mydomain.com"&gt;
* </pre>
*
*
* ## Global Object
* The <angular/> script creates a single global variable `angular` in the global namespace. All
* APIs are bound to fields of this global object.
*
*/
function angularInit(config){
if (config.autobind) {
// TODO default to the source of angular.js
+9
View File
@@ -1,4 +1,13 @@
var browserSingleton;
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$browser
* @requires $log
*
* @description
* Represents the browser.
*/
angularService('$browser', function($log){
if (!browserSingleton) {
browserSingleton = new Browser(
+145 -27
View File
@@ -19,7 +19,21 @@ function Browser(location, document, head, XHR, $log) {
var outstandingRequestCount = 0;
var outstandingRequestCallbacks = [];
self.xhr = function(method, url, post, callback){
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#xhr
* @methodOf angular.service.$browser
*
* @param {string} method Requested method (get|post|put|delete|head|json)
* @param {string} url Requested url
* @param {string=} post Post data to send
* @param {function(number, string)} callback Function that will be called on response
*
* @description
* Send ajax request
*/
self.xhr = function(method, url, post, callback) {
if (isFunction(post)) {
callback = post;
post = _null;
@@ -63,7 +77,15 @@ function Browser(location, document, head, XHR, $log) {
}
};
self.notifyWhenNoOutstandingRequests = function(callback){
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#notifyWhenNoOutstandingRequests
* @methodOf angular.service.$browser
*
* @param {function()} callback Function that will be called when no outstanding request
*/
self.notifyWhenNoOutstandingRequests = function(callback) {
if (outstandingRequestCount === 0) {
callback();
} else {
@@ -75,27 +97,51 @@ function Browser(location, document, head, XHR, $log) {
// Poll Watcher API
//////////////////////////////////////////////////////////////
var pollFns = [];
function poll(){
foreach(pollFns, function(pollFn){ pollFn(); });
}
self.poll = poll;
/**
* Adds a function to the list of functions that poller periodically executes
* @return {Function} the added function
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#poll
* @methodOf angular.service.$browser
*/
self.addPollFn = function(/**Function*/fn){
self.poll = function() {
foreach(pollFns, function(pollFn){ pollFn(); });
};
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#addPollFn
* @methodOf angular.service.$browser
*
* @param {function()} fn Poll function to add
*
* @description
* Adds a function to the list of functions that poller periodically executes
*
* @returns {function()} the added function
*/
self.addPollFn = function(fn) {
pollFns.push(fn);
return fn;
};
/**
* Configures the poller to run in the specified intervals, using the specified setTimeout fn and
* kicks it off.
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#startPoller
* @methodOf angular.service.$browser
*
* @param {number} interval How often should browser call poll functions (ms)
* @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
*
* @description
* Configures the poller to run in the specified intervals, using the specified
* setTimeout fn and kicks it off.
*/
self.startPoller = function(/**number*/interval, /**Function*/setTimeout){
self.startPoller = function(interval, setTimeout) {
(function check(){
poll();
self.poll();
setTimeout(check, interval);
})();
};
@@ -103,15 +149,39 @@ function Browser(location, document, head, XHR, $log) {
//////////////////////////////////////////////////////////////
// URL API
//////////////////////////////////////////////////////////////
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#setUrl
* @methodOf angular.service.$browser
*
* @param {string} url New url
*
* @description
* Sets browser's url
*/
self.setUrl = function(url) {
var existingURL = location.href;
if (!existingURL.match(/#/)) existingURL += '#';
if (!url.match(/#/)) url += '#';
location.href = url;
};
self.getUrl = function() {
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#getUrl
* @methodOf angular.service.$browser
*
* @description
* Get current browser's url
*
* @returns {string} Browser's url
*/
self.getUrl = function() {
return location.href;
};
};
//////////////////////////////////////////////////////////////
// Cookies API
@@ -121,19 +191,28 @@ function Browser(location, document, head, XHR, $log) {
var lastCookieString = '';
/**
* The cookies method provides a 'private' low level access to browser cookies. It is not meant to
* be used directly, use the $cookie service instead.
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#cookies
* @methodOf angular.service.$browser
*
* @param {string=} name Cookie name
* @param {string=} value Cokkie value
*
* @description
* The cookies method provides a 'private' low level access to browser cookies.
* It is not meant to be used directly, use the $cookie service instead.
*
* The return values vary depending on the arguments that the method was called with as follows:
* <ul><li>
* cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it
* </li><li>
* cookies(name, value) -> set name to value, if value is undefined delete the cookie
* </li><li>
* cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)
* </li></ul>
* <ul>
* <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li>
* <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li>
* <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li>
* </ul>
*
* @returns {Object} Hash of all cookies (if called without any parameter)
*/
self.cookies = function (/**string*/name, /**string*/value){
self.cookies = function (name, value) {
var cookieLength, cookieArray, i, keyValue;
if (name) {
@@ -175,7 +254,30 @@ function Browser(location, document, head, XHR, $log) {
// Misc API
//////////////////////////////////////////////////////////////
var hoverListener = noop;
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#hover
* @methodOf angular.service.$browser
*
* @description
* Set hover listener.
*
* @param {function(Object, boolean)} listener Function that will be called when hover event
* occurs.
*/
self.hover = function(listener) { hoverListener = listener; };
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#bind
* @methodOf angular.service.$browser
*
* @description
* Register hover function to real browser
*/
self.bind = function() {
document.bind("mouseover", function(event){
hoverListener(jqLite(msie ? event.srcElement : event.target), true);
@@ -189,9 +291,16 @@ function Browser(location, document, head, XHR, $log) {
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#addCss
* @methodOf angular.service.$browser
*
* @param {string} url Url to css file
* @description
* Adds a stylesheet tag to the head.
*/
self.addCss = function(/**string*/url) {
self.addCss = function(url) {
var link = jqLite(rawDocument.createElement('link'));
link.attr('rel', 'stylesheet');
link.attr('type', 'text/css');
@@ -201,9 +310,18 @@ function Browser(location, document, head, XHR, $log) {
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$browser#addJs
* @methodOf angular.service.$browser
*
* @param {string} url Url to js file
* @param {string=} dom_id Optional id for the script tag
*
* @description
* Adds a script tag to the head.
*/
self.addJs = function(/**string*/url, /**string*/dom_id) {
self.addJs = function(url, dom_id) {
var script = jqLite(rawDocument.createElement('script'));
script.attr('type', 'text/javascript');
script.attr('src', url);
+52
View File
@@ -109,6 +109,58 @@ Compiler.prototype = {
};
},
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:eval-order
*
* @description
* Normally the view is updated from top to bottom. This usually is
* not a problem, but under some circumstances the values for data
* is not available until after the full view is computed. If such
* values are needed before they are computed the order of
* evaluation can be change using ng:eval-order
*
* @element ANY
* @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant
*
* @exampleDescription
* try changing the invoice and see that the Total will lag in evaluation
* @example
<div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div>
<div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div>
<table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]">
<tr>
<td>QTY</td>
<td>Description</td>
<td>Cost</td>
<td>Total</td>
<td></td>
</tr>
<tr ng:repeat="item in items">
<td><input name="item.qty"/></td>
<td><input name="item.desc"/></td>
<td><input name="item.cost"/></td>
<td>{{item.total = item.qty * item.cost | currency}}</td>
<td><a href="" ng:click="items.$remove(item)">X</a></td>
</tr>
<tr>
<td colspan="3"><a href="" ng:click="items.$add()">add</a></td>
<td>{{ items.$sum('total') | currency }}</td>
</tr>
</table>
*
* @scenario
it('should check ng:format', function(){
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99');
input('item.qty').enter('2');
expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99');
expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98');
});
*/
templatize: function(element, elementIndex, priority){
var self = this,
widget,
+3 -2
View File
@@ -19,8 +19,9 @@ function createInjector(providerScope, providers, cache) {
* string: return an instance for the injection key.
* array of keys: returns an array of instances.
* function: look at $inject property of function to determine instances
* and then call the function with instances and scope. Any
* additional arguments are passed on to function.
* and then call the function with instances and `scope`. Any
* additional arguments (`args`) are appended to the function
* arguments.
* object: initialize eager providers and publish them the ones with publish here.
* none: same as object but use providerScope as place to publish.
*/
+25
View File
@@ -1,11 +1,36 @@
var array = [].constructor;
/**
* @workInProgress
* @ngdoc function
* @name angular.toJson
* @function
*
* @description
* Serializes the input into a JSON formated string.
*
* @param {Object|Array|Date|string|number} obj Input to jsonify.
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
* @returns {string} Jsonified string representing `obj`.
*/
function toJson(obj, pretty) {
var buf = [];
toJsonArray(buf, obj, pretty ? "\n " : _null, []);
return buf.join('');
}
/**
* @workInProgress
* @ngdoc function
* @name angular.fromJson
* @function
*
* @description
* Deserializes a string in the JSON format.
*
* @param {string} json JSON string to deserialize.
* @returns {Object|Array|Date|string|number} Deserialized thingy.
*/
function fromJson(json) {
if (!json) return json;
try {
+348 -9
View File
@@ -101,9 +101,134 @@ function expressionCompile(exp){
}
function errorHandlerFor(element, error) {
elementError(element, NG_EXCEPTION, isDefined(error) ? toJson(error) : error);
elementError(element, NG_EXCEPTION, isDefined(error) ? formatError(error) : error);
}
/**
* @workInProgress
* @ngdoc overview
* @name angular.scope
*
* @description
* Scope is a JavaScript object and the execution context for expressions. You can think about
* scopes as JavaScript objects that have extra APIs for registering watchers. A scope is the model
* in the model-view-controller design pattern.
*
* A few other characteristics of scopes:
*
* - Scopes can be nested. A scope (prototypically) inherits properties from its parent scope.
* - Scopes can be attached (bound) to the HTML DOM tree (the view).
* - A scope {@link angular.scope.$become becomes} `this` for a controller.
* - Scope's {@link angular.scope.$eval $eval} is used to update its view.
* - Scopes can {@link angular.scope.$watch watch} properties and fire events.
*
* # Basic Operations
* Scopes can be created by calling {@link angular.scope() angular.scope()} or by compiling HTML.
*
* {@link angular.widget Widgets} and data bindings register listeners on the current scope to get
* notified of changes to the scope state. When notified, these listeners push the updated state
* through to the DOM.
*
* Here is a simple scope snippet to show how you can interact with the scope.
* <pre>
var scope = angular.scope();
scope.salutation = 'Hello';
scope.name = 'World';
expect(scope.greeting).toEqual(undefined);
scope.$watch('name', function(){
this.greeting = this.salutation + ' ' + this.name + '!';
});
expect(scope.greeting).toEqual('Hello World!');
scope.name = 'Misko';
// scope.$eval() will propagate the change to listeners
expect(scope.greeting).toEqual('Hello World!');
scope.$eval();
expect(scope.greeting).toEqual('Hello Misko!');
* </pre>
*
* # Inheritance
* A scope can inherit from a parent scope, as in this example:
* <pre>
var parent = angular.scope();
var child = angular.scope(parent);
parent.salutation = "Hello";
child.name = "World";
expect(child.salutation).toEqual('Hello');
child.salutation = "Welcome";
expect(child.salutation).toEqual('Welcome');
expect(parent.salutation).toEqual('Hello');
* </pre>
*
* # Dependency Injection
* Scope also acts as a simple dependency injection framework.
*
* **TODO**: more info needed
*
* # When scopes are evaluated
* Anyone can update a scope by calling its {@link angular.scope.$eval $eval()} method. By default
* angular widgets listen to user change events (e.g. the user enters text into text field), copy
* the data from the widget to the scope (the MVC model), and then call the `$eval()` method on the
* root scope to update dependents. This creates a spreadsheet-like behavior: the bound views update
* immediately as the user types into the text field.
*
* Similarly, when a request to fetch data from a server is made and the response comes back, the
* data is written into the model and then $eval() is called to push updates through to the view and
* any other dependents.
*
* Because a change in the model that's triggered either by user input or by server response calls
* `$eval()`, it is unnecessary to call `$eval()` from within your controller. The only time when
* calling `$eval()` is needed, is when implementing a custom widget or service.
*
* Because scopes are inherited, the child scope `$eval()` overrides the parent `$eval()` method.
* So to update the whole page you need to call `$eval()` on the root scope as `$root.$eval()`.
*
* Note: A widget that creates scopes (i.e. {@link angular.widget.@ng:repeat ng:repeat}) is
* responsible for forwarding `$eval()` calls from the parent to those child scopes. That way,
* calling $eval() on the root scope will update the whole page.
*
* @exampleDescription
* This example demonstrates scope inheritance and property overriding.
*
* In this example, the root scope encompasses the whole HTML DOM tree. This scope has `salutation`,
* `name`, and `names` properties. The {@link angular.widget@ng:repeat ng:repeat} creates a child
* scope, one for each element in the names array. The repeater also assigns $index and name into
* the child scope.
*
* Notice that:
*
* - While the name is set in the child scope it does not change the name defined in 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.
*
* @example
<ul ng:init="salutation='Hello'; name='Misko'; names=['World', 'Earth']">
<li ng:repeat="name in names">
{{$index}}: {{salutation}} {{name}}!
</li>
</ul>
<pre>
$index={{$index}}
salutation={{salutation}}
name={{name}}</pre>
@scenario
it('should inherit the salutation property and override the name property', function() {
expect(using('.doc-example-live').repeater('li').row(0)).
toEqual(['0', 'Hello', 'World']);
expect(using('.doc-example-live').repeater('li').row(1)).
toEqual(['1', 'Hello', 'Earth']);
expect(using('.doc-example-live').element('pre').text()).
toBe('$index=\nsalutation=Hello\nname=Misko');
});
*/
function createScope(parent, providers, instanceCache) {
function Parent(){}
parent = Parent.prototype = (parent || {});
@@ -115,11 +240,64 @@ function createScope(parent, providers, instanceCache) {
'this': instance,
$id: (scopeId++),
$parent: parent,
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$bind
* @function
*
* @description
* Binds a function `fn` to the current scope. See: {@link angular.bind}.
<pre>
var scope = angular.scope();
var fn = scope.$bind(function(){
return this;
});
expect(fn()).toEqual(scope);
</pre>
*
* @param {function()} fn Function to be bound.
*/
$bind: bind(instance, bind, instance),
$get: bind(instance, getter, instance),
$set: bind(instance, setter, instance),
$eval: function $eval(exp) {
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$eval
* @function
*
* @description
* Without the `exp` parameter triggers an eval cycle, for this scope and it's child scopes.
*
* With the `exp` parameter, compiles the expression to a function and calls it with `this` set
* to the current scope and returns the result.
*
* # Example
<pre>
var scope = angular.scope();
scope.a = 1;
scope.b = 2;
expect(scope.$eval('a+b')).toEqual(3);
expect(scope.$eval(function(){ return this.a + this.b; })).toEqual(3);
scope.$onEval('sum = a+b');
expect(scope.sum).toEqual(undefined);
scope.$eval();
expect(scope.sum).toEqual(3);
</pre>
*
* @param {(string|function())=} exp An angular expression to be compiled to a function or a js
* function.
*
* @returns {*} The result of calling compiled `exp` with `this` set to the current scope.
*/
$eval: function(exp) {
var type = typeof exp;
var i, iSize;
var j, jSize;
@@ -145,6 +323,44 @@ function createScope(parent, providers, instanceCache) {
}
},
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$tryEval
* @function
*
* @description
* Evaluates the expression in the context of the current scope just like
* {@link angular.scope.$eval()} with expression parameter, but also wraps it in a try/catch
* block.
*
* If exception is thrown then `exceptionHandler` is used to handle the exception.
*
* # Example
<pre>
var scope = angular.scope();
scope.error = function(){ throw 'myerror'; };
scope.$exceptionHandler = function(e) {this.lastException = e; };
expect(scope.$eval('error()'));
expect(scope.lastException).toEqual('myerror');
this.lastException = null;
expect(scope.$eval('error()'), function(e) {this.lastException = e; });
expect(scope.lastException).toEqual('myerror');
var body = angular.element(window.document.body);
expect(scope.$eval('error()'), body);
expect(body.attr('ng-exception')).toEqual('"myerror"');
expect(body.hasClass('ng-exception')).toEqual(true);
</pre>
*
* @param {string|function()} expression Angular expression to evaluate.
* @param {function()|DOMElement} exceptionHandler Function to be called or DOMElement to be
* decorated.
* @returns {*} The result of `expression` evaluation.
*/
$tryEval: function (expression, exceptionHandler) {
var type = typeof expression;
try {
@@ -165,14 +381,58 @@ function createScope(parent, providers, instanceCache) {
}
},
$watch: function(watchExp, listener, exceptionHandler) {
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$watch
* @function
*
* @description
* Registers `listener` as a callback to be executed every time the `watchExp` changes. Be aware
* that callback gets, by default, called upon registration, this can be prevented via the
* `initRun` parameter.
*
* # Example
<pre>
var scope = angular.scope();
scope.name = 'misko';
scope.counter = 0;
expect(scope.counter).toEqual(0);
scope.$watch('name', 'counter = counter + 1');
expect(scope.counter).toEqual(1);
scope.$eval();
expect(scope.counter).toEqual(1);
scope.name = 'adam';
scope.$eval();
expect(scope.counter).toEqual(2);
</pre>
*
* @param {function()|string} watchExp Expression that should be evaluated and checked for
* change during each eval cycle. Can be an angular string expression or a function.
* @param {function()|string} listener Function (or angular string expression) that gets called
* every time the value of the `watchExp` changes. The function will be called with two
* parameters, `newValue` and `oldValue`.
* @param {(function()|DOMElement)=} [exceptionHanlder=angular.service.$exceptionHandler] Handler
* that gets called when `watchExp` or `listener` throws an exception. If a DOMElement is
* specified as handler, the element gets decorated by angular with the information about the
* exception.
* @param {boolean=} [initRun=true] Flag that prevents the first execution of the listener upon
* registration.
*
*/
$watch: function(watchExp, listener, exceptionHandler, initRun) {
var watch = expressionCompile(watchExp),
last = {};
last = watch.call(instance);
listener = expressionCompile(listener);
function watcher(){
function watcher(firstRun){
var value = watch.call(instance),
// we have to save the value because listener can call ourselves => inf loop
lastValue = last;
if (last !== value) {
if (firstRun || lastValue !== value) {
last = value;
instance.$tryEval(function(){
return listener.call(instance, value, lastValue);
@@ -180,9 +440,36 @@ function createScope(parent, providers, instanceCache) {
}
}
instance.$onEval(PRIORITY_WATCH, watcher);
watcher();
if (isUndefined(initRun)) initRun = true;
if (initRun) watcher(true);
},
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$onEval
* @function
*
* @description
* Evaluates the `expr` expression in the context of the current scope during each
* {@link angular.scope.$eval eval cycle}.
*
* # Example
<pre>
var scope = angular.scope();
scope.counter = 0;
scope.$onEval('counter = counter + 1');
expect(scope.counter).toEqual(0);
scope.$eval();
expect(scope.counter).toEqual(1);
</pre>
*
* @param {number} [priority=0] Execution priority. Lower priority numbers get executed first.
* @param {string|function()} expr Angular expression or function to be executed.
* @param {(function()|DOMElement)=} [exceptionHandler=angular.service.$exceptionHandler] Handler
* function to call or DOM element to decorate when an exception occurs.
*
*/
$onEval: function(priority, expr, exceptionHandler){
if (!isNumber(priority)) {
exceptionHandler = expr;
@@ -202,6 +489,12 @@ function createScope(parent, providers, instanceCache) {
});
},
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$postEval
* @function
*/
$postEval: function(expr) {
if (expr) {
var fn = expressionCompile(expr);
@@ -216,6 +509,36 @@ function createScope(parent, providers, instanceCache) {
}
},
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$become
* @function
* @deprecated This method will be removed before 1.0
*
* @description
* Modifies the scope to act like an instance of the given class by:
*
* - copying the class's prototype methods
* - applying the class's initialization function to the scope instance (without using the new
* operator)
*
* That makes the scope be a `this` for the given class's methods — effectively an instance of
* the given class with additional (scope) stuff. A scope can later `$become` another class.
*
* `$become` gets used to make the current scope act like an instance of a controller class.
* This allows for use of a controller class in two ways.
*
* - as an ordinary JavaScript class for standalone testing, instantiated using the new
* operator, with no attached view.
* - as a controller for an angular model stored in a scope, "instantiated" by
* `scope.$become(ControllerClass)`.
*
* Either way, the controller's methods refer to the model variables like `this.name`. When
* stored in a scope, the model supports data binding. When bound to a view, {{name}} in the
* HTML template refers to the same variable.
*/
$become: function(Class) {
if (isFunction(Class)) {
instance.constructor = Class;
@@ -231,9 +554,25 @@ function createScope(parent, providers, instanceCache) {
}
},
$new: function(Class) {
/**
* @workInProgress
* @ngdoc function
* @name angular.scope.$new
* @function
*
* @description
* Creates a new {@link angular.scope scope}, that:
*
* - is a child of the current scope
* - will {@link angular.scope.$become $become} of type specified via `constructor`
*
* @param {function()} constructor Constructor function of the type the new scope should assume.
* @returns {Object} The newly created child scope.
*
*/
$new: function(constructor) {
var child = createScope(instance);
child.$become.apply(instance, concat([Class], arguments, 1));
child.$become.apply(instance, concat([constructor], arguments, 1));
instance.$onEval(child.$eval);
return child;
}
+33 -158
View File
@@ -1,4 +1,5 @@
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:init
*
@@ -27,6 +28,7 @@ angularDirective("ng:init", function(expression){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:controller
*
@@ -105,6 +107,7 @@ angularDirective("ng:controller", function(expression){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:eval
*
@@ -147,6 +150,7 @@ angularDirective("ng:eval", function(expression){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:bind
*
@@ -155,7 +159,7 @@ angularDirective("ng:eval", function(expression){
* HTML element with the value of the given expression and kept it up to
* date when the expression's value changes. Usually you just write
* {{expression}} and let <angular/> compile it into
* <span ng:bind="expression"></span> at bootstrap time.
* `<span ng:bind="expression"></span>` at bootstrap time.
*
* @element ANY
* @param {expression} expression to eval.
@@ -182,7 +186,7 @@ angularDirective("ng:bind", function(expression, element){
oldElement = this.hasOwnProperty($$element) ? this.$element : _undefined;
this.$element = element;
value = this.$tryEval(expression, function(e){
error = toJson(e);
error = formatError(e);
});
this.$element = oldElement;
// If we are HTML than save the raw HTML data so that we don't
@@ -250,6 +254,7 @@ function compileBindTemplate(template){
}
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:bind-template
*
@@ -302,6 +307,7 @@ var REMOVE_ATTRIBUTES = {
'checked':'checked'
};
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:bind-attr
*
@@ -313,7 +319,7 @@ var REMOVE_ATTRIBUTES = {
* `ng:bind-attr` in the HTML since embedding
* <tt ng:non-bindable>{{expression}}</tt> into the
* attribute directly is the preferred way. The attributes get
* translated into <span ng:bind-attr="{attr:expression}"/> at
* translated into `<span ng:bind-attr="{attr:expression}"/>` at
* bootstrap time.
*
* This HTML snippet is preferred way of working with `ng:bind-attr`
@@ -378,162 +384,9 @@ angularDirective("ng:bind-attr", function(expression){
};
});
/**
* @ngdoc directive
* @name angular.directive.ng:non-bindable
*
* @description
* Sometimes it is necessary to write code which looks like
* bindings but which should be left alone by <angular/>.
* Use `ng:non-bindable` to ignore a chunk of HTML.
*
* @element ANY
* @param {string} ignore
*
* @exampleDescription
* In this example there are two location where
* <tt ng:non-bindable>{{1 + 2}}</tt> is present, but the one
* wrapped in `ng:non-bindable` is left alone
* @example
<div>Normal: {{1 + 2}}</div>
<div ng:non-bindable>Ignored: {{1 + 2}}</div>
*
* @scenario
it('should check ng:non-bindable', function(){
expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
expect(using('.doc-example-live').element('div:last').text()).
toMatch(/1 \+ 2/);
});
*/
angularWidget("@ng:non-bindable", noop);
/**
* @ngdoc directive
* @name angular.directive.ng:repeat
*
* @description
* `ng:repeat` instantiates a template once per item from a
* collection. The collection is enumerated with
* `ng:repeat-index` attribute starting from 0. Each template
* instance gets its own scope where the given loop variable
* is set to the current collection item and `$index` is set
* to the item index or key.
*
* NOTE: `ng:repeat` looks like a directive, but is actually a
* attribute widget.
*
* @element ANY
* @param {repeat} repeat_expression to itterate over.
*
* * `variable in expression`, where variable is the user
* defined loop variable and expression is a scope expression
* giving the collection to enumerate. For example:
* `track in cd.tracks`.
* * `(key, value) in expression`, where key and value can
* be any user defined identifiers, and expression is the
* scope expression giving the collection to enumerate.
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
*
* Special properties set on the local scope:
* * {number} $index - iterator offset of the repeated element (0..length-1)
* * {string} $position - position of the repeated element in the iterator ('first', 'middle', 'last')
*
* @exampleDescription
* This example initializes the scope to a list of names and
* than uses `ng:repeat` to display every person.
* @example
<div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
I have {{friends.length}} friends. They are:
<ul>
<li ng:repeat="friend in friends">
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
</li>
</ul>
</div>
* @scenario
it('should check ng:repeat', function(){
var r = using('.doc-example-live').repeater('ul li');
expect(r.count()).toBe(2);
expect(r.row(0)).toEqual(["1","John","25"]);
expect(r.row(1)).toEqual(["2","Mary","28"]);
});
*/
angularWidget("@ng:repeat", function(expression, element){
element.removeAttr('ng:repeat');
element.replaceWith(this.comment("ng:repeat: " + expression));
var template = this.compile(element);
return function(reference){
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
lhs, rhs, valueIdent, keyIdent;
if (! match) {
throw "Expected ng:repeat in form of 'item in collection' but got '" +
expression + "'.";
}
lhs = match[1];
rhs = match[2];
match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
if (!match) {
throw "'item' in 'item in collection' should be identifier or (key, value) but got '" +
keyValue + "'.";
}
valueIdent = match[3] || match[1];
keyIdent = match[2];
var children = [], currentScope = this;
this.$onEval(function(){
var index = 0,
childCount = children.length,
lastElement = reference,
collection = this.$tryEval(rhs, reference),
is_array = isArray(collection),
collectionLength = 0,
childScope,
key;
if (is_array) {
collectionLength = collection.length;
} else {
for (key in collection)
if (collection.hasOwnProperty(key))
collectionLength++;
}
for (key in collection) {
if (!is_array || collection.hasOwnProperty(key)) {
if (index < childCount) {
// reuse existing child
childScope = children[index];
childScope[valueIdent] = collection[key];
if (keyIdent) childScope[keyIdent] = key;
} else {
// grow children
childScope = template(quickClone(element), createScope(currentScope));
childScope[valueIdent] = collection[key];
if (keyIdent) childScope[keyIdent] = key;
lastElement.after(childScope.$element);
childScope.$index = index;
childScope.$position = index == 0 ?
'first' :
(index == collectionLength - 1 ? 'last' : 'middle');
childScope.$element.attr('ng:repeat-index', index);
childScope.$init();
children.push(childScope);
}
childScope.$eval();
lastElement = childScope.$element;
index ++;
}
}
// shrink children
while(children.length > index) {
children.pop().$element.remove();
}
}, reference);
};
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:click
*
@@ -577,6 +430,7 @@ angularDirective("ng:click", function(expression, element){
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:submit
*
@@ -624,6 +478,7 @@ angularDirective("ng:submit", function(expression, element) {
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:watch
*
@@ -649,6 +504,7 @@ angularDirective("ng:submit", function(expression, element) {
expect(using('.doc-example-live').binding('counter')).toBe('3');
});
*/
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
angularDirective("ng:watch", function(expression, element){
return function(element){
var self = this;
@@ -678,6 +534,7 @@ function ngClass(selector) {
}
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:class
*
@@ -714,6 +571,7 @@ function ngClass(selector) {
angularDirective("ng:class", ngClass(function(){return true;}));
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:class-odd
*
@@ -749,6 +607,7 @@ angularDirective("ng:class", ngClass(function(){return true;}));
angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:class-even
*
@@ -784,6 +643,7 @@ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;}));
angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;}));
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:show
*
@@ -821,6 +681,7 @@ angularDirective("ng:show", function(expression, element){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:hide
*
@@ -858,19 +719,33 @@ angularDirective("ng:hide", function(expression, element){
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:style
*
* @description
* The ng:style allows you to set CSS style on an HTML element conditionally.
*
* @element ANY
* @param {expression} expression to eval.
* @param {expression} expression which evals to an object whes key's are
* CSS style names and values are coresponding values for those
* CSS keys.
*
* @exampleDescription
* @example
<input type="button" value="set" ng:click="myStyle={color:'red'}">
<input type="button" value="clear" ng:click="myStyle={}">
<br/>
<span ng:style="myStyle">Sample Text</span>
<pre>myStyle={{myStyle}}</pre>
*
* @scenario
it('should check ng:style', function(){
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
element('.doc-example-live :button[value=set]').click();
expect(element('.doc-example-live span').css('color')).toBe('red');
element('.doc-example-live :button[value=clear]').click();
expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
});
*/
angularDirective("ng:style", function(expression, element){
+10 -2
View File
@@ -1,4 +1,5 @@
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.currency
* @function
@@ -33,6 +34,7 @@ angularFilter.currency = function(amount){
};
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.number
* @function
@@ -42,8 +44,8 @@ angularFilter.currency = function(amount){
*
* If the input is not a number empty string is returned.
*
* @param {(number|string)} number Number to format.
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to. Default 2.
* @param {number|string} number Number to format.
* @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
* @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
*
* @example
@@ -148,6 +150,7 @@ var NUMBER_STRING = /^\d+$/;
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.date
* @function
@@ -229,6 +232,7 @@ angularFilter.date = function(date, format) {
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.json
* @function
@@ -267,6 +271,7 @@ angularFilter.json = function(object) {
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.lowercase
* @function
@@ -277,6 +282,7 @@ angularFilter.lowercase = lowercase;
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.uppercase
* @function
@@ -287,6 +293,7 @@ angularFilter.uppercase = uppercase;
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.html
* @function
@@ -375,6 +382,7 @@ angularFilter.html = function(html, option){
/**
* @workInProgress
* @ngdoc filter
* @name angular.filter.linky
* @function
+9 -4
View File
@@ -8,6 +8,7 @@ var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/;
angularFormatter.noop = formatter(identity, identity);
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.json
*
@@ -32,13 +33,14 @@ angularFormatter.noop = formatter(identity, identity);
angularFormatter.json = formatter(toJson, fromJson);
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.boolean
*
* @description
* Use boolean formatter if you wish to store the data as boolean.
*
* @returns Convert to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
* @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
*
* @example
* Enter truthy text:
@@ -56,13 +58,14 @@ angularFormatter.json = formatter(toJson, fromJson);
angularFormatter['boolean'] = formatter(toString, toBoolean);
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.number
*
* @description
* Use number formatter if you wish to convert the user entered string to a number.
*
* @returns parse string to number.
* @returns {number} Number from the parsed string.
*
* @example
* Enter valid number:
@@ -85,13 +88,14 @@ angularFormatter.number = formatter(toString, function(obj){
});
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.list
*
* @description
* Use number formatter if you wish to convert the user entered string to a number.
* Use list formatter if you wish to convert the user entered string to an array.
*
* @returns parse string to number.
* @returns {Array} Array parsed from the entered string.
*
* @example
* Enter a list of items:
@@ -122,6 +126,7 @@ angularFormatter.list = formatter(
);
/**
* @workInProgress
* @ngdoc formatter
* @name angular.formatter.trim
*
+53
View File
@@ -68,6 +68,59 @@ angularTextMarkup('OPTION', function(text, textNode, parentElement){
}
});
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:href
*
* @description
* Using <angular/> markup like {{hash}} in an href attribute makes
* the page open to a wrong URL, ff the user clicks that link before
* angular has a chance to replace the {{hash}} with actual URL, the
* link will be broken and will most likely return a 404 error.
* The `ng:href` solves this problem by placing the `href` in the
* `ng:` namespace.
*
* The buggy way to write it:
* <pre>
* <a href="http://www.gravatar.com/avatar/{{hash}}"/>
* </pre>
*
* The correct way to write it:
* <pre>
* <a ng:href="http://www.gravatar.com/avatar/{{hash}}"/>
* </pre>
*
* @element ANY
* @param {template} template any string which can contain `{{}}` markup.
*/
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:src
*
* @description
* Using <angular/> markup like `{{hash}}` in a `src` attribute doesn't
* work right: The browser will fetch from the URL with the literal
* text `{{hash}}` until <angular/> replaces the expression inside
* `{{hash}}`. The `ng:src` attribute solves this problem by placing
* the `src` attribute in the `ng:` namespace.
*
* The buggy way to write it:
* <pre>
* <img src="http://www.gravatar.com/avatar/{{hash}}"/>
* </pre>
*
* The correct way to write it:
* <pre>
* <img ng:src="http://www.gravatar.com/avatar/{{hash}}"/>
* </pre>
*
* @element ANY
* @param {template} template any string which can contain `{{}}` markup.
*/
var NG_BIND_ATTR = 'ng:bind-attr';
var SPECIAL_ATTRS = {'ng:src': 'src', 'ng:href': 'href'};
angularAttrMarkup('{{}}', function(value, name, element){
+33 -52
View File
@@ -67,10 +67,7 @@ function lex(text, parseStringsForObjects){
tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
index += 1;
} else {
throw "Lexer Error: Unexpected next character [" +
text.substring(index) +
"] in expression '" + text +
"' at column '" + (index+1) + "'.";
throwError("Unexpected next character ", index, index+1);
}
}
lastCh = ch;
@@ -103,6 +100,16 @@ function lex(text, parseStringsForObjects){
function isExpOperator(ch) {
return ch == '-' || ch == '+' || isNumber(ch);
}
function throwError(error, start, end) {
end = end || index;
throw Error("Lexer Error: " + error + " at column" +
(isDefined(start) ?
"s " + start + "-" + index + " [" + text.substring(start, end) + "]" :
" " + end) +
" in expression [" + text + "].");
}
function readNumber() {
var number = "";
var start = index;
@@ -121,7 +128,7 @@ function lex(text, parseStringsForObjects){
} else if (isExpOperator(ch) &&
(!peekCh || !isNumber(peekCh)) &&
number.charAt(number.length - 1) == 'e') {
throw 'Lexer found invalid exponential value "' + text + '"';
throwError('Invalid exponent');
} else {
break;
}
@@ -151,7 +158,7 @@ function lex(text, parseStringsForObjects){
}
tokens.push({index:start, text:ident, fn:fn, json: OPERATORS[ident]});
}
function readString(quote) {
var start = index;
index++;
@@ -165,9 +172,7 @@ function lex(text, parseStringsForObjects){
if (ch == 'u') {
var hex = text.substring(index + 1, index + 5);
if (!hex.match(/[\da-f]{4}/i))
throw "Lexer Error: Invalid unicode escape [\\u" +
hex + "] starting at column '" +
start + "' in expression '" + text + "'.";
throwError( "Invalid unicode escape [\\u" + hex + "]");
index += 4;
string += String.fromCharCode(parseInt(hex, 16));
} else {
@@ -194,9 +199,7 @@ function lex(text, parseStringsForObjects){
}
index++;
}
throw "Lexer Error: Unterminated quote [" +
text.substring(start) + "] starting at column '" +
(start+1) + "' in expression '" + text + "'.";
throwError("Unterminated quote", start);
}
function readRegexp(quote) {
var start = index;
@@ -227,9 +230,7 @@ function lex(text, parseStringsForObjects){
}
index++;
}
throw "Lexer Error: Unterminated RegExp [" +
text.substring(start) + "] starting at column '" +
(start+1) + "' in expression '" + text + "'.";
throwError("Unterminated RegExp", start);
}
}
@@ -244,21 +245,21 @@ function parser(text, json){
statements: statements,
validator: validator,
filter: filter,
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
watch: watch
};
///////////////////////////////////
function error(msg, token) {
throw "Token '" + token.text +
"' is " + msg + " at column='" +
(token.index + 1) + "' of expression '" +
text + "' starting at '" + text.substring(token.index) + "'.";
function throwError(msg, token) {
throw Error("Parse Error: Token '" + token.text +
"' " + msg + " at column " +
(token.index + 1) + " of expression [" +
text + "] starting at [" + text.substring(token.index) + "].");
}
function peekToken() {
if (tokens.length === 0)
throw "Unexpected end of expression: " + text;
throw Error("Unexpected end of expression: " + text);
return tokens[0];
}
@@ -279,10 +280,7 @@ function parser(text, json){
if (token) {
if (json && !token.json) {
index = token.index;
throw "Expression at column='" +
token.index + "' of expression '" +
text + "' starting at '" + text.substring(token.index) +
"' is not valid json.";
throwError("is not valid json", token);
}
tokens.shift();
this.currentToken = token;
@@ -293,11 +291,7 @@ function parser(text, json){
function consume(e1){
if (!expect(e1)) {
var token = peek();
throw "Expecting '" + e1 + "' at column '" +
(token.index+1) + "' in '" +
text + "' got '" +
text.substring(token.index) + "'.";
throwError("is unexpected, expecting [" + e1 + "]", peek());
}
}
@@ -319,8 +313,7 @@ function parser(text, json){
function assertAllConsumed(){
if (tokens.length !== 0) {
throw "Did not understand '" + text.substring(tokens[0].index) +
"' while evaluating '" + text + "'.";
throwError("is extra token not part of expression", tokens[0]);
}
}
@@ -386,18 +379,7 @@ function parser(text, json){
}
function expression(){
return throwStmt();
}
function throwStmt(){
if (expect('throw')) {
var throwExp = assignment();
return function (self) {
throw throwExp(self);
};
} else {
return assignment();
}
return assignment();
}
function assignment(){
@@ -405,9 +387,8 @@ function parser(text, json){
var token;
if (token = expect('=')) {
if (!left.isAssignable) {
throw "Left hand side '" +
text.substring(0, token.index) + "' of assignment '" +
text.substring(token.index) + "' is not assignable.";
throwError("implies assignment but [" +
text.substring(0, token.index) + "] can not be assigned to", token);
}
var ident = function(){return left.isAssignable;};
return binaryFn(ident, token.fn, logicalOR());
@@ -497,8 +478,7 @@ function parser(text, json){
instance = instance[key];
}
if (typeof instance != $function) {
throw "Function '" + token.text + "' at column '" +
(token.index+1) + "' in '" + text + "' is not defined.";
throwError("should be a function", token);
}
return instance;
}
@@ -517,7 +497,7 @@ function parser(text, json){
var token = expect();
primary = token.fn;
if (!primary) {
error("not a primary expression", token);
throwError("not a primary expression", token);
}
}
var next;
@@ -529,7 +509,7 @@ function parser(text, json){
} else if (next.text === '.') {
primary = fieldAccess(primary);
} else {
throw "IMPOSSIBLE";
throwError("IMPOSSIBLE");
}
}
return primary;
@@ -624,6 +604,7 @@ function parser(text, json){
};
}
//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular)
function watch () {
var decl = [];
while(hasTokens()) {
+436 -27
View File
@@ -8,11 +8,67 @@ function angularServiceInject(name, fn, inject, eager) {
angularService(name, fn, {$inject:inject, $creation:eager});
}
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$window
*
* @description
* Is reference to the browser's <b>window</b> object. While <b>window</b>
* is globally available in JavaScript, it causes testability problems, because
* it is a global variable. In <b><angular/></b> we always refer to it through the
* $window service, so it may be overriden, removed or mocked for testing.
*
* All expressions are evaluated with respect to current scope so they don't
* suffer from window globality.
*
* @example
<input ng:init="greeting='Hello World!'" type="text" name="greeting" />
<button ng:click="$window.alert(greeting)">ALERT</button>
*/
angularServiceInject("$window", bind(window, identity, window), [], EAGER_PUBLISHED);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$document
* @requires $window
*
* @description
* Reference to the browser window.document, but wrapped into angular.element().
*/
angularServiceInject("$document", function(window){
return jqLite(window.document);
}, ['$window'], EAGER_PUBLISHED);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$location
* @requires $browser
*
* @property {string} href
* @property {string} protocol
* @property {string} host
* @property {number} port
* @property {string} path
* @property {Object.<string|boolean>} search
* @property {string} hash
* @property {string} hashPath
* @property {Object.<string|boolean>} hashSearch
*
* @description
* Parses the browser location url and makes it available to your application.
* Any changes to the url are reflected into $location service and changes to
* $location are reflected to url.
* Notice that using browser's forward/back buttons changes the $location.
*
* @example
<a href="#">clear hash</a> |
<a href="#myPath?name=misko">test hash</a><br/>
<input type='text' name="$location.hash"/>
<pre>$location = {{$location}}</pre>
*/
angularServiceInject("$location", function(browser) {
var scope = this,
location = {toString:toString, update:update, updateHash: updateHash},
@@ -39,6 +95,12 @@ angularServiceInject("$location", function(browser) {
// PUBLIC METHODS
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$location#update
* @methodOf angular.service.$location
*
* @description
* Update location object
* Does not immediately update the browser
* Browser is updated at the end of $eval()
@@ -69,7 +131,13 @@ angularServiceInject("$location", function(browser) {
}
/**
* Update location hash
* @workInProgress
* @ngdoc method
* @name angular.service.$location#updateHash
* @methodOf angular.service.$location
*
* @description
* Update location hash part
* @see update()
*
* @example
@@ -99,9 +167,13 @@ angularServiceInject("$location", function(browser) {
}
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$location#toString
* @methodOf angular.service.$location
*
* @description
* Returns string representation - href
*
* @return {string} Location's href property
*/
function toString() {
updateLocation();
@@ -176,7 +248,9 @@ angularServiceInject("$location", function(browser) {
*/
function composeHash(loc) {
var hashSearch = toKeyValue(loc.hashSearch);
return escape(loc.hashPath) + (hashSearch ? '?' + hashSearch : '');
//TODO: temporary fix for issue #158
return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') +
(hashSearch ? '?' + hashSearch : '');
}
/**
@@ -224,23 +298,127 @@ angularServiceInject("$location", function(browser) {
}, ['$browser'], EAGER_PUBLISHED);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$log
* @requires $window
*
* @description
* Is simple service for logging. Default implementation writes the message
* into the browser's console (if present).
*
* This is useful for debugging.
*
* @example
<p>Reload this page with open console, enter text and hit the log button...</p>
Message:
<input type="text" name="message" value="Hello World!"/>
<button ng:click="$log.log(message)">log</button>
<button ng:click="$log.warn(message)">warn</button>
<button ng:click="$log.info(message)">info</button>
<button ng:click="$log.error(message)">error</button>
*/
angularServiceInject("$log", function($window){
var console = $window.console || {log: noop, warn: noop, info: noop, error: noop},
log = console.log || noop;
return {
log: bind(console, log),
warn: bind(console, console.warn || log),
info: bind(console, console.info || log),
error: bind(console, console.error || log)
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$log#log
* @methodOf angular.service.$log
*
* @description
* Write a log message
*/
log: consoleLog('log'),
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$log#warn
* @methodOf angular.service.$log
*
* @description
* Write a warning message
*/
warn: consoleLog('warn'),
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$log#info
* @methodOf angular.service.$log
*
* @description
* Write an information message
*/
info: consoleLog('info'),
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$log#error
* @methodOf angular.service.$log
*
* @description
* Write an error message
*/
error: consoleLog('error')
};
function consoleLog(type) {
var console = $window.console || {};
var logFn = console[type] || console.log || noop;
if (logFn.apply) {
return function(){
var args = [];
foreach(arguments, function(arg){
args.push(formatError(arg));
});
return logFn.apply(console, args);
};
} else {
// we are IE, in which case there is nothing we can do
return logFn;
}
}
}, ['$window'], EAGER_PUBLISHED);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$exceptionHandler
* @requires $log
*
* @description
* Any uncaught exception in <angular/> is delegated to this service.
* The default implementation simply delegates to $log.error which logs it into
* the browser console.
*
* When unit testing it is useful to have uncaught exceptions propagate
* to the test so the test will fail rather than silently log the exception
* to the browser console. For this purpose you can override this service with
* a simple rethrow.
*
* @example
*/
angularServiceInject('$exceptionHandler', function($log){
return function(e) {
$log.error(e);
};
}, ['$log'], EAGER_PUBLISHED);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$hover
* @requires $browser
* @requires $document
*
* @description
*
* @example
*/
angularServiceInject("$hover", function(browser, document) {
var tooltip, self = this, error, width = 300, arrowWidth = 10, body = jqLite(document[0].body);
browser.hover(function(element, show){
@@ -287,9 +465,16 @@ angularServiceInject("$hover", function(browser, document) {
});
}, ['$browser', '$document'], EAGER);
/* Keeps references to all invalid widgets found during validation. Can be queried to find if there
* are invalid widgets currently displayed
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$invalidWidgets
*
* @description
* Keeps references to all invalid widgets found during validation.
* Can be queried to find whether there are any invalid widgets currently displayed.
*
* @example
*/
angularServiceInject("$invalidWidgets", function(){
var invalidWidgets = [];
@@ -373,7 +558,60 @@ function switchRouteMatcher(on, when, dstName) {
return match ? dst : _null;
}
angularServiceInject('$route', function(location){
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$route
* @requires $location
*
* @property {Object} current Name of the current route
* @property {Array.<Object>} routes List of configured routes
*
* @description
* Watches $location.hashPath and tries to map the hash to an existing route
* definition. It is used for deep-linking URLs to controllers and views (HTML partials).
*
* $route is typically used in conjunction with ng:include widget.
*
* @example
<p>
This example shows how changing the URL hash causes the <tt>$route</tt>
to match a route against the URL, and the <tt>[[ng:include]]</tt> pulls in the partial.
Try changing the URL in the input box to see changes.
</p>
<script>
angular.service('myApp', function($route) {
$route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl});
$route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl});
$route.onChange(function() {
$route.current.scope.params = $route.current.params;
});
}, {$inject: ['$route']});
function BookCntl() {
this.name = "BookCntl";
}
function ChapterCntl() {
this.name = "ChapterCntl";
}
</script>
Chose:
<a href="#/Book/Moby">Moby</a> |
<a href="#/Book/Moby/ch/1">Moby: Ch1</a> |
<a href="#/Book/Gatsby">Gatsby</a> |
<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/>
<input type="text" name="$location.hashPath" size="80" />
<pre>$location={{$location}}</pre>
<pre>$route.current.template={{$route.current.template}}</pre>
<pre>$route.current.params={{$route.current.params}}</pre>
<pre>$route.current.scope.name={{$route.current.scope.name}}</pre>
<hr/>
<ng:include src="$route.current.template" scope="$route.current.scope"/>
*/
angularServiceInject('$route', function(location) {
var routes = {},
onChange = [],
matcher = switchRouteMatcher,
@@ -381,8 +619,35 @@ angularServiceInject('$route', function(location){
dirty = 0,
$route = {
routes: routes,
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$route#onChange
* @methodOf angular.service.$route
*
* @param {function()} fn Function that will be called on route change
*
* @description
* Register a handler function that will be called when route changes
*/
onChange: bind(onChange, onChange.push),
when:function (path, params){
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$route#when
* @methodOf angular.service.$route
*
* @param {string} path Route path (matched against $location.hash)
* @param {Object} params Mapping information to be assigned to `$route.current` on route
* match.
* @returns {Object} route object
*
* @description
* Add new route
*/
when:function (path, params) {
if (angular.isUndefined(path)) return routes;
var route = routes[path];
if (!route) route = routes[path] = {};
@@ -415,6 +680,18 @@ angularServiceInject('$route', function(location){
return $route;
}, ['$location'], EAGER_PUBLISHED);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$xhr
* @requires $browser
* @requires $error
* @requires $log
*
* @description
*
* @example
*/
angularServiceInject('$xhr', function($browser, $error, $log){
var self = this;
return function(method, url, post, callback){
@@ -446,12 +723,34 @@ angularServiceInject('$xhr', function($browser, $error, $log){
};
}, ['$browser', '$xhr.error', '$log']);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$xhr.error
* @requires $log
*
* @description
*
* @example
*/
angularServiceInject('$xhr.error', function($log){
return function(request, response){
$log.error('ERROR: XHR: ' + request.url, request, response);
};
}, ['$log']);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$xhr.bulk
* @requires $xhr
* @requires $xhr.error
* @requires $log
*
* @description
*
* @example
*/
angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
var requests = [],
scope = this;
@@ -502,6 +801,16 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
return bulkXHR;
}, ['$xhr', '$xhr.error', '$log']);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$xhr.cache
* @requires $xhr
*
* @description
*
* @example
*/
angularServiceInject('$xhr.cache', function($xhr){
var inflight = {}, self = this;
function cache(method, url, post, callback, verifyCache){
@@ -546,18 +855,76 @@ angularServiceInject('$xhr.cache', function($xhr){
return cache;
}, ['$xhr.bulk']);
/**
* @workInProgress
* @ngdoc service
* @name angular.service.$resource
* @requires $xhr
*
* @description
* Is a factory which creates a resource object which lets you interact with
* <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer" target="_blank">RESTful</a>
* server-side data sources.
* Resource object has action methods which provide high-level behaviors without
* the need to interact with the low level $xhr or XMLHttpRequest().
*
* @example
<script>
function BuzzController($resource) {
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt:'json', callback:'JSON_CALLBACK'},
{get:{method:'JSON', params:{visibility:'@self'}}, replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}}
);
}
BuzzController.prototype = {
fetch: function() {
this.activities = this.Activity.get({userId:this.userId});
},
expandReplies: function(activity) {
activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id});
}
};
BuzzController.$inject = ['$resource'];
</script>
<div ng:controller="BuzzController">
<input name="userId" value="googlebuzz"/>
<button ng:click="fetch()">fetch</button>
<hr/>
<div ng:repeat="item in activities.data.items">
<h1 style="font-size: 15px;">
<img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a>
<a href="#" ng:click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a>
</h1>
{{item.object.content | html}}
<div ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;">
<img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/>
<a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}}
</div>
</div>
</div>
*/
angularServiceInject('$resource', function($xhr){
var resource = new ResourceFactory($xhr);
return bind(resource, resource.route);
}, ['$xhr.cache']);
/**
* $cookies service provides read/write access to the browser cookies. Currently only session
* cookies are supported.
*
* Only a simple Object is exposed and by adding or removing properties to/from this object, new
* cookies are created or deleted from the browser at the end of the current eval.
* @workInProgress
* @ngdoc service
* @name angular.service.$cookies
* @requires $browser
*
* @description
* Provides read/write access to browser's cookies.
*
* Only a simple Object is exposed and by adding or removing properties to/from
* this object, new cookies are created/deleted at the end of current $eval.
*
* @example
*/
angularServiceInject('$cookies', function($browser) {
var rootScope = this,
@@ -630,23 +997,65 @@ angularServiceInject('$cookies', function($browser) {
}
}, ['$browser'], EAGER_PUBLISHED);
/**
* $cookieStore provides a key-value (string-object) storage that is backed by session cookies.
* Objects put or retrieved from this storage are automatically serialized or deserialized.
* @workInProgress
* @ngdoc service
* @name angular.service.$cookieStore
* @requires $cookies
*
* @description
* Provides a key-value (string-object) storage, that is backed by session cookies.
* Objects put or retrieved from this storage are automatically serialized or
* deserialized by angular's toJson/fromJson.
* @example
*/
angularServiceInject('$cookieStore', function($store) {
return {
get: function(/**string*/key) {
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$cookieStore#get
* @methodOf angular.service.$cookieStore
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value.
*/
get: function(key) {
return fromJson($store[key]);
},
put: function(/**string*/key, /**Object*/value) {
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$cookieStore#put
* @methodOf angular.service.$cookieStore
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {Object} value Value to be stored.
*/
put: function(key, value) {
$store[key] = toJson(value);
},
remove: function(/**string*/key) {
/**
* @workInProgress
* @ngdoc method
* @name angular.service.$cookieStore#remove
* @methodOf angular.service.$cookieStore
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
delete $store[key];
}
};
+9
View File
@@ -2,6 +2,7 @@ extend(angularValidator, {
'noop': function() { return _null; },
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.regexp
* @description
@@ -36,6 +37,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.number
* @description
@@ -88,6 +90,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.integer
* @description
@@ -133,6 +136,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.date
* @description
@@ -166,6 +170,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.email
* @description
@@ -195,6 +200,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.phone
* @description
@@ -227,6 +233,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.url
* @description
@@ -256,6 +263,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.json
* @description
@@ -288,6 +296,7 @@ extend(angularValidator, {
},
/**
* @workInProgress
* @ngdoc validator
* @name angular.validator.asynchronous
* @description
+298 -4
View File
@@ -1,4 +1,5 @@
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.HTML
*
@@ -165,6 +166,101 @@ function compileValidator(expr) {
return parser(expr).validator()();
}
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.@ng:validate
*
* @description
* The `ng:validate` attribute widget validates the user input. If the input does not pass
* validation, the `ng-validation-error` CSS class and the `ng:error` attribute are set on the input
* element. Check out {@link angular.validator validators} to find out more.
*
* @param {string} validator The name of a built-in or custom {@link angular.validator validator} to
* to be used.
*
* @element INPUT
* @css ng-validation-error
*
* @exampleDescription
* This example shows how the input element becomes red when it contains invalid input. Correct
* the input to make the error disappear.
*
* @example
I don't validate:
<input type="text" name="value" value="NotANumber"><br/>
I need an integer or nothing:
<input type="text" name="value" ng:validate="integer"><br/>
*
* @scenario
it('should check ng:validate', function(){
expect(element('.doc-example-live :input:last').attr('className')).
toMatch(/ng-validation-error/);
input('value').enter('123');
expect(element('.doc-example-live :input:last').attr('className')).
not().toMatch(/ng-validation-error/);
});
*/
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.@ng:required
*
* @description
* The `ng:required` attribute widget validates that the user input is present. It is a special case
* of the {@link angular.widget.@ng:validate ng:validate} attribute widget.
*
* @element INPUT
* @css ng-validation-error
*
* @exampleDescription
* This example shows how the input element becomes red when it contains invalid input. Correct
* the input to make the error disappear.
*
* @example
I cannot be blank: <input type="text" name="value" ng:required><br/>
*
* @scenario
it('should check ng:required', function(){
expect(element('.doc-example-live :input').attr('className')).toMatch(/ng-validation-error/);
input('value').enter('123');
expect(element('.doc-example-live :input').attr('className')).not().toMatch(/ng-validation-error/);
});
*/
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.@ng:format
*
* @description
* The `ng:format` attribute widget formats stored data to user-readable text and parses the text
* back to the stored form. You might find this useful for example if you collect user input in a
* text field but need to store the data in the model as a list. Check out
* {@link angular.formatter formatters} to learn more.
*
* @param {string} formatter The name of the built-in or custom {@link angular.formatter formatter}
* to be used.
*
* @element INPUT
*
* @exampleDescription
* This example shows how the user input is converted from a string and internally represented as an
* array.
*
* @example
Enter a comma separated list of items:
<input type="text" name="list" ng:format="list" value="table, chairs, plate">
<pre>list={{list}}</pre>
*
* @scenario
it('should check ng:format', function(){
expect(binding('list')).toBe('list=["table","chairs","plate"]');
input('list').enter(',,, a ,,,');
expect(binding('list')).toBe('list=["a"]');
});
*/
function valueAccessor(scope, element) {
var validatorName = element.attr('ng:validate') || NOOP,
validator = compileValidator(validatorName),
@@ -320,6 +416,40 @@ function radioInit(model, view, element) {
view.set(modelValue);
}
/**
* @workInProgress
* @ngdoc directive
* @name angular.directive.ng:change
*
* @description
* The directive executes an expression whenever the input widget changes.
*
* @element INPUT
* @param {expression} expression to execute.
*
* @exampleDescription
* @example
<div ng:init="checkboxCount=0; textCount=0"></div>
<input type="text" name="text" ng:change="textCount = 1 + textCount">
changeCount {{textCount}}<br/>
<input type="checkbox" name="checkbox" ng:change="checkboxCount = 1 + checkboxCount">
changeCount {{checkboxCount}}<br/>
*
* @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');
expect(binding('textCount')).toBe('1');
expect(binding('checkboxCount')).toBe('0');
using('.doc-example-live').input('checkbox').check();
expect(binding('textCount')).toBe('1');
expect(binding('checkboxCount')).toBe('1');
});
*/
function inputWidget(events, modelAccessor, viewAccessor, initFn) {
return function(element) {
var scope = this,
@@ -374,6 +504,7 @@ angularWidget('option', function(){
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.ng:include
*
@@ -385,6 +516,7 @@ angularWidget('option', function(){
*
* @param {string} src expression evaluating to URL.
* @param {Scope=} [scope=new_child_scope] expression evaluating to angular.scope
* @param {string=} onload Expression to evaluate when a new partial is loaded.
*
* @example
* <select name="url">
@@ -412,7 +544,8 @@ angularWidget('option', function(){
angularWidget('ng:include', function(element){
var compiler = this,
srcExp = element.attr("src"),
scopeExp = element.attr("scope") || '';
scopeExp = element.attr("scope") || '',
onloadExp = element[0].getAttribute('onload') || ''; //workaround for jquery bug #7537
if (element[0]['ng:compiled']) {
this.descend(true);
this.directives(true);
@@ -437,13 +570,15 @@ angularWidget('ng:include', function(element){
});
this.$watch(function(){return changeCounter;}, function(){
var src = this.$eval(srcExp),
useScope = this.$eval(scopeExp);
useScope = this.$eval(scopeExp);
if (src) {
xhr('GET', src, function(code, response){
element.html(response);
childScope = useScope || createScope(scope);
compiler.compile(element)(element, childScope);
childScope.$init();
scope.$eval(onloadExp);
});
} else {
childScope = null;
@@ -455,6 +590,7 @@ angularWidget('ng:include', function(element){
});
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.ng:switch
*
@@ -578,7 +714,7 @@ var ngSwitch = angularWidget('ng:switch', function (element){
* changing the location or causing page reloads, e.g.:
* <a href="" ng:click="model.$save()">Save</a>
*/
angular.widget('a', function() {
angularWidget('a', function() {
this.descend(true);
this.directives(true);
@@ -589,4 +725,162 @@ angular.widget('a', function() {
});
}
};
});
});
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.@ng:repeat
*
* @description
* `ng:repeat` instantiates a template once per item from a collection. The collection is enumerated
* with `ng:repeat-index` attribute starting from 0. Each template instance gets its own scope where
* the given loop variable is set to the current collection item and `$index` is set to the item
* index or key.
*
* There are special properties exposed on the local scope of each template instance:
*
* * `$index` `{number}` iterator offset of the repeated element (0..length-1)
* * `$position` {string} position of the repeated element in the iterator. One of: `'first'`,
* `'middle'` or `'last'`.
*
* NOTE: `ng:repeat` looks like a directive, but is actually an attribute widget.
*
* @element ANY
* @param {string} repeat_expression The expression indicating how to enumerate a collection. Two
* formats are currently supported:
*
* * `variable in expression` where variable is the user defined loop variable and `expression`
* is a scope expression giving the collection to enumerate.
*
* For example: `track in cd.tracks`.
* * `(key, value) in expression` where `key` and `value` can be any user defined identifiers,
* and `expression` is the scope expression giving the collection to enumerate.
*
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
*
* @exampleDescription
* This example initializes the scope to a list of names and
* than uses `ng:repeat` to display every person.
* @example
<div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]">
I have {{friends.length}} friends. They are:
<ul>
<li ng:repeat="friend in friends">
[{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
</li>
</ul>
</div>
* @scenario
it('should check ng:repeat', function(){
var r = using('.doc-example-live').repeater('ul li');
expect(r.count()).toBe(2);
expect(r.row(0)).toEqual(["1","John","25"]);
expect(r.row(1)).toEqual(["2","Mary","28"]);
});
*/
angularWidget("@ng:repeat", function(expression, element){
element.removeAttr('ng:repeat');
element.replaceWith(this.comment("ng:repeat: " + expression));
var template = this.compile(element);
return function(reference){
var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
lhs, rhs, valueIdent, keyIdent;
if (! match) {
throw Error("Expected ng:repeat in form of 'item in collection' but got '" +
expression + "'.");
}
lhs = match[1];
rhs = match[2];
match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
if (!match) {
throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" +
keyValue + "'.");
}
valueIdent = match[3] || match[1];
keyIdent = match[2];
var children = [], currentScope = this;
this.$onEval(function(){
var index = 0,
childCount = children.length,
lastElement = reference,
collection = this.$tryEval(rhs, reference),
is_array = isArray(collection),
collectionLength = 0,
childScope,
key;
if (is_array) {
collectionLength = collection.length;
} else {
for (key in collection)
if (collection.hasOwnProperty(key))
collectionLength++;
}
for (key in collection) {
if (!is_array || collection.hasOwnProperty(key)) {
if (index < childCount) {
// reuse existing child
childScope = children[index];
childScope[valueIdent] = collection[key];
if (keyIdent) childScope[keyIdent] = key;
} else {
// grow children
childScope = template(quickClone(element), createScope(currentScope));
childScope[valueIdent] = collection[key];
if (keyIdent) childScope[keyIdent] = key;
lastElement.after(childScope.$element);
childScope.$index = index;
childScope.$position = index == 0 ?
'first' :
(index == collectionLength - 1 ? 'last' : 'middle');
childScope.$element.attr('ng:repeat-index', index);
childScope.$init();
children.push(childScope);
}
childScope.$eval();
lastElement = childScope.$element;
index ++;
}
}
// shrink children
while(children.length > index) {
children.pop().$element.remove();
}
}, reference);
};
});
/**
* @workInProgress
* @ngdoc widget
* @name angular.widget.@ng:non-bindable
*
* @description
* Sometimes it is necessary to write code which looks like bindings but which should be left alone
* by angular. Use `ng:non-bindable` to make angular ignore a chunk of HTML.
*
* NOTE: `ng:non-bindable` looks like a directive, but is actually an attribute widget.
*
* @element ANY
*
* @exampleDescription
* In this example there are two location where a siple binding (`{{}}`) is present, but the one
* wrapped in `ng:non-bindable` is left alone.
*
* @example
<div>Normal: {{1 + 2}}</div>
<div ng:non-bindable>Ignored: {{1 + 2}}</div>
*
* @scenario
it('should check ng:non-bindable', function(){
expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
expect(using('.doc-example-live').element('div:last').text()).
toMatch(/1 \+ 2/);
});
*/
angularWidget("@ng:non-bindable", noop);
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
tests=$1
if [[ $tests = "" ]]; then
tests="all"
fi
java -Xmx1g -jar lib/jstestdriver/JsTestDriver.jar --config jsTestDriver-coverage.conf --testOutput=tmp/lcov --tests "$tests"
+6 -7
View File
@@ -276,15 +276,15 @@ BinderTest.prototype.testIfTextBindingThrowsErrorDecorateTheSpan = function(){
a.scope.$eval();
var span = childNode(doc, 0);
assertTrue(span.hasClass('ng-exception'));
assertEquals('ErrorMsg1', fromJson(span.text()));
assertEquals('"ErrorMsg1"', span.attr('ng-exception'));
assertTrue(!!span.text().match(/ErrorMsg1/));
assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/));
a.scope.$set('error.throw', function(){throw "MyError";});
a.scope.$eval();
span = childNode(doc, 0);
assertTrue(span.hasClass('ng-exception'));
assertTrue(span.text(), span.text().match('MyError') !== null);
assertEquals('"MyError"', span.attr('ng-exception'));
assertEquals('MyError', span.attr('ng-exception'));
a.scope.$set('error.throw', function(){return "ok";});
a.scope.$eval();
@@ -438,13 +438,12 @@ BinderTest.prototype.testActionOnAHrefThrowsError = function(){
var model = {books:[]};
var c = this.compile('<a ng:click="action()">Add Phone</a>', model);
c.scope.action = function(){
throw {a:'abc', b:2};
throw new Error('MyError');
};
var input = c.node;
browserTrigger(input, 'click');
var error = fromJson(input.attr('ng-exception'));
assertEquals("abc", error.a);
assertEquals(2, error.b);
var error = input.attr('ng-exception');
assertTrue(!!error.match(/MyError/));
assertTrue("should have an error class", input.hasClass('ng-exception'));
// TODO: I think that exception should never get cleared so this portion of test makes no sense
+15 -8
View File
@@ -74,7 +74,7 @@ describe('json', function(){
});
it('should serialize $ properties', function() {
var obj = {$a: 'a'}
var obj = {$a: 'a'};
expect(angular.toJson(obj)).toEqual('{"$a":"a"}');
});
@@ -118,31 +118,38 @@ describe('json', function(){
describe('security', function(){
it('should not allow naked expressions', function(){
expect(function(){fromJson('1+2');}).toThrow("Did not understand '+2' while evaluating '1+2'.");
expect(function(){fromJson('1+2');}).
toThrow(new Error("Parse Error: Token '+' is extra token not part of expression at column 2 of expression [1+2] starting at [+2]."));
});
it('should not allow naked expressions group', function(){
expect(function(){fromJson('(1+2)');}).toThrow("Expression at column='0' of expression '(1+2)' starting at '(1+2)' is not valid json.");
expect(function(){fromJson('(1+2)');}).
toThrow(new Error("Parse Error: Token '(' is not valid json at column 1 of expression [(1+2)] starting at [(1+2)]."));
});
it('should not allow expressions in objects', function(){
expect(function(){fromJson('{a:abc()}');}).toThrow("Expression at column='3' of expression '{a:abc()}' starting at 'abc()}' is not valid json.");
expect(function(){fromJson('{a:abc()}');}).
toThrow(new Error("Parse Error: Token 'abc' is not valid json at column 4 of expression [{a:abc()}] starting at [abc()}]."));
});
it('should not allow expressions in arrays', function(){
expect(function(){fromJson('[1+2]');}).toThrow("Expression at column='2' of expression '[1+2]' starting at '+2]' is not valid json.");
expect(function(){fromJson('[1+2]');}).
toThrow(new Error("Parse Error: Token '+' is not valid json at column 3 of expression [[1+2]] starting at [+2]]."));
});
it('should not allow vars', function(){
expect(function(){fromJson('[1, x]');}).toThrow("Expression at column='4' of expression '[1, x]' starting at 'x]' is not valid json.");
expect(function(){fromJson('[1, x]');}).
toThrow(new Error("Parse Error: Token 'x' is not valid json at column 5 of expression [[1, x]] starting at [x]]."));
});
it('should not allow dereference', function(){
expect(function(){fromJson('["".constructor]');}).toThrow("Expression at column='3' of expression '[\"\".constructor]' starting at '.constructor]' is not valid json.");
expect(function(){fromJson('["".constructor]');}).
toThrow(new Error("Parse Error: Token '.' is not valid json at column 4 of expression [[\"\".constructor]] starting at [.constructor]]."));
});
it('should not allow expressions ofter valid json', function(){
expect(function(){fromJson('[].constructor');}).toThrow("Expression at column='2' of expression '[].constructor' starting at '.constructor' is not valid json.");
expect(function(){fromJson('[].constructor');}).
toThrow(new Error("Parse Error: Token '.' is not valid json at column 3 of expression [[].constructor] starting at [.constructor]."));
});
});
+4 -12
View File
@@ -158,11 +158,11 @@ describe('parser', function() {
it('should throws exception for invalid exponent', function() {
expect(function() {
lex("0.5E-");
}).toThrow('Lexer found invalid exponential value "0.5E-"');
}).toThrow(new Error('Lexer Error: Invalid exponent at column 4 in expression [0.5E-].'));
expect(function() {
lex("0.5E-A");
}).toThrow('Lexer found invalid exponential value "0.5E-A"');
}).toThrow(new Error('Lexer Error: Invalid exponent at column 4 in expression [0.5E-A].'));
});
it('should tokenize number starting with a dot', function() {
@@ -173,7 +173,7 @@ describe('parser', function() {
it('should throw error on invalid unicode', function() {
expect(function() {
lex("'\\u1''bla'");
}).toThrow("Lexer Error: Invalid unicode escape [\\u1''b] starting at column '0' in expression ''\\u1''bla''.");
}).toThrow(new Error("Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']."));
});
});
@@ -225,7 +225,7 @@ describe('parser', function() {
expect(function() {
scope.$eval("1|nonExistant");
}).toThrow("Function 'nonExistant' at column '3' in '1|nonExistant' is not defined.");
}).toThrow(new Error("Parse Error: Token 'nonExistant' should be a function at column 3 of expression [1|nonExistant] starting at [nonExistant]."));
scope.$set('offset', 3);
expect(scope.$eval("'abcd'|upper._case")).toEqual("ABCD");
@@ -312,14 +312,6 @@ describe('parser', function() {
expect(scope.$eval(";;1;;")).toEqual(1);
});
it('should evaluate throw', function() {
scope.$set('e', 'abc');
expect(function() {
scope.$eval("throw e");
}).toThrow('abc');
});
it('should evaluate object methods in correct context (this)', function() {
var C = function () {
this.a = 123;
+39 -5
View File
@@ -96,6 +96,40 @@ describe('scope/model', function(){
model.$eval();
expect(count).toEqual(1);
});
it('should run listener upon registration by default', function() {
var model = createScope();
var count = 0,
nameNewVal = 'crazy val 1',
nameOldVal = 'crazy val 2';
model.$watch('name', function(newVal, oldVal){
count ++;
nameNewVal = newVal;
nameOldVal = oldVal;
});
expect(count).toBe(1);
expect(nameNewVal).not.toBeDefined();
expect(nameOldVal).not.toBeDefined();
});
it('should not run listener upon registration if flag is passed in', function() {
var model = createScope();
var count = 0,
nameNewVal = 'crazy val 1',
nameOldVal = 'crazy val 2';
model.$watch('name', function(newVal, oldVal){
count ++;
nameNewVal = newVal;
nameOldVal = oldVal;
}, undefined, false);
expect(count).toBe(0);
expect(nameNewVal).toBe('crazy val 1');
expect(nameOldVal).toBe('crazy val 2');
});
});
describe('$bind', function(){
@@ -109,17 +143,17 @@ describe('scope/model', function(){
describe('$tryEval', function(){
it('should report error on element', function(){
var scope = createScope();
scope.$tryEval('throw "myerror";', function(error){
scope.$tryEval(function(){throw "myError";}, function(error){
scope.error = error;
});
expect(scope.error).toEqual('myerror');
expect(scope.error).toEqual('myError');
});
it('should report error on visible element', function(){
var element = jqLite('<div></div>');
var scope = createScope();
scope.$tryEval('throw "myError"', element);
expect(element.attr('ng-exception')).toEqual('"myError"'); // errors are jsonified
scope.$tryEval(function(){throw "myError";}, element);
expect(element.attr('ng-exception')).toEqual('myError');
expect(element.hasClass('ng-exception')).toBeTruthy();
});
@@ -129,7 +163,7 @@ describe('scope/model', function(){
scope.$exceptionHandler = function(e){
this.error = e;
};
scope.$tryEval('throw "myError"');
scope.$tryEval(function(){throw "myError";});
expect(scope.error).toEqual("myError");
});
});
+1 -78
View File
@@ -1,4 +1,4 @@
describe("directives", function(){
describe("directive", function(){
var compile, model, element;
@@ -128,83 +128,6 @@ describe("directives", function(){
expect(input.checked).toEqual(true);
});
it('should ng:non-bindable', function(){
var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>');
scope.$set('name', 'misko');
scope.$eval();
expect(element.text()).toEqual('');
});
describe('ng:repeat', function() {
it('should ng:repeat over array', function(){
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
Array.prototype.extraProperty = "should be ignored";
scope.items = ['misko', 'shyam'];
scope.$eval();
expect(element.text()).toEqual('misko;shyam;');
delete Array.prototype.extraProperty;
scope.items = ['adam', 'kai', 'brad'];
scope.$eval();
expect(element.text()).toEqual('adam;kai;brad;');
scope.items = ['brad'];
scope.$eval();
expect(element.text()).toEqual('brad;');
});
it('should ng:repeat over object', function(){
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
scope.$set('items', {misko:'swe', shyam:'set'});
scope.$eval();
expect(element.text()).toEqual('misko:swe;shyam:set;');
});
it('should error on wrong parsing of ng:repeat', function(){
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
var log = "";
log += element.attr('ng-exception') + ';';
log += element.hasClass('ng-exception') + ';';
expect(log).toEqual("\"Expected ng:repeat in form of 'item in collection' but got 'i dont parse'.\";true;");
});
it('should expose iterator offset as $index when iterating over arrays', function() {
var scope = compile('<ul><li ng:repeat="item in items" ' +
'ng:bind="item + $index + \'|\'"></li></ul>');
scope.items = ['misko', 'shyam', 'frodo'];
scope.$eval();
expect(element.text()).toEqual('misko0|shyam1|frodo2|');
});
it('should expose iterator offset as $index when iterating over objects', function() {
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>');
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
scope.$eval();
expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|');
});
it('should expose iterator position as $position when iterating over arrays', function() {
var scope = compile('<ul><li ng:repeat="item in items" ' +
'ng:bind="item + \':\' + $position + \'|\'"></li></ul>');
scope.items = ['misko', 'shyam', 'doug', 'frodo'];
scope.$eval();
expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|');
});
it('should expose iterator position as $position when iterating over objects', function() {
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>');
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
scope.$eval();
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
});
});
it('should ng:watch', function(){
var scope = compile('<div ng:watch="i: count = count + 1" ng:init="count = 0">');
scope.$eval();
+36
View File
@@ -70,6 +70,42 @@ describe("service", function(){
scope.$log.info();
scope.$log.error();
});
describe('Error', function(){
var e, $log, $console, errorArgs;
beforeEach(function(){
e = new Error('');
e.message = undefined;
e.sourceURL = undefined;
e.line = undefined;
e.stack = undefined;
$console = angular.service('$log')({console:{error:function(){
errorArgs = arguments;
}}});
});
it('should pass error if does not have trace', function(){
$console.error('abc', e);
expect(errorArgs).toEqual(['abc', e]);
});
it('should print stack', function(){
e.stack = 'stack';
$console.error('abc', e);
expect(errorArgs).toEqual(['abc', 'stack']);
});
it('should print line', function(){
e.message = 'message';
e.sourceURL = 'sourceURL';
e.line = '123';
$console.error('abc', e);
expect(errorArgs).toEqual(['abc', 'message\nsourceURL:123']);
});
});
});
describe("$exceptionHandler", function(){
+94
View File
@@ -11,6 +11,7 @@ describe("widget", function(){
(before||noop).apply(scope);
if (parent) parent.append(element);
scope.$init();
return scope;
};
});
@@ -532,6 +533,19 @@ describe("widget", function(){
// we need to have real events on the scopes.
expect(element.text()).toEqual('4');
});
it('should evaluate onload expression when a partial is loaded', function() {
var element = jqLite('<ng:include src="url" onload="loaded = true"></ng:include>');
var scope = angular.compile(element);
expect(scope.loaded).not.toBeDefined();
scope.url = 'myUrl';
scope.$inject('$xhr.cache').data.myUrl = {value:'my partial'};
scope.$init();
expect(element.text()).toEqual('my partial');
expect(scope.loaded).toBe(true);
});
});
describe('a', function() {
@@ -568,5 +582,85 @@ describe("widget", function(){
expect(document.location.href).toEqual(orgLocation);
});
});
describe('@ng:repeat', function() {
it('should ng:repeat over array', function(){
var scope = compile('<ul><li ng:repeat="item in items" ng:init="suffix = \';\'" ng:bind="item + suffix"></li></ul>');
Array.prototype.extraProperty = "should be ignored";
scope.items = ['misko', 'shyam'];
scope.$eval();
expect(element.text()).toEqual('misko;shyam;');
delete Array.prototype.extraProperty;
scope.items = ['adam', 'kai', 'brad'];
scope.$eval();
expect(element.text()).toEqual('adam;kai;brad;');
scope.items = ['brad'];
scope.$eval();
expect(element.text()).toEqual('brad;');
});
it('should ng:repeat over object', function(){
var scope = compile('<ul><li ng:repeat="(key, value) in items" ng:bind="key + \':\' + value + \';\' "></li></ul>');
scope.$set('items', {misko:'swe', shyam:'set'});
scope.$eval();
expect(element.text()).toEqual('misko:swe;shyam:set;');
});
it('should error on wrong parsing of ng:repeat', function(){
var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>');
var log = "";
log += element.attr('ng-exception') + ';';
log += element.hasClass('ng-exception') + ';';
expect(log.match(/Expected ng:repeat in form of 'item in collection' but got 'i dont parse'./)).toBeTruthy();
});
it('should expose iterator offset as $index when iterating over arrays', function() {
var scope = compile('<ul><li ng:repeat="item in items" ' +
'ng:bind="item + $index + \'|\'"></li></ul>');
scope.items = ['misko', 'shyam', 'frodo'];
scope.$eval();
expect(element.text()).toEqual('misko0|shyam1|frodo2|');
});
it('should expose iterator offset as $index when iterating over objects', function() {
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
'ng:bind="key + \':\' + val + $index + \'|\'"></li></ul>');
scope.items = {'misko':'m', 'shyam':'s', 'frodo':'f'};
scope.$eval();
expect(element.text()).toEqual('misko:m0|shyam:s1|frodo:f2|');
});
it('should expose iterator position as $position when iterating over arrays', function() {
var scope = compile('<ul><li ng:repeat="item in items" ' +
'ng:bind="item + \':\' + $position + \'|\'"></li></ul>');
scope.items = ['misko', 'shyam', 'doug', 'frodo'];
scope.$eval();
expect(element.text()).toEqual('misko:first|shyam:middle|doug:middle|frodo:last|');
});
it('should expose iterator position as $position when iterating over objects', function() {
var scope = compile('<ul><li ng:repeat="(key, val) in items" ' +
'ng:bind="key + \':\' + val + \':\' + $position + \'|\'"></li></ul>');
scope.items = {'misko':'m', 'shyam':'s', 'doug':'d', 'frodo':'f'};
scope.$eval();
expect(element.text()).toEqual('misko:m:first|shyam:s:middle|doug:d:middle|frodo:f:last|');
});
});
describe('@ng:non-bindable', function() {
it('should prevent compilation of the owning element and its children', function(){
var scope = compile('<div ng:non-bindable><span ng:bind="name"></span></div>');
scope.$set('name', 'misko');
scope.$eval();
expect(element.text()).toEqual('');
});
});
});
+2 -2
View File
@@ -1,4 +1,4 @@
# <angular/> build config file
---
version: 0.9.3
codename: cold-resistance
version: 0.9.4
codename: total-recall