Compare commits

...

12 Commits

Author SHA1 Message Date
Igor Minar 7989c7d24a cutting the 0.9.7 sonic-scream release 2010-12-10 17:08:52 -08:00
Igor Minar 5c36f466e1 fixing release notes 2010-12-10 17:08:10 -08:00
Igor Minar f8151afd90 improve doc app scrollbars 2010-12-10 17:04:56 -08:00
Igor Minar 74120eaa0f updating release notes 2010-12-10 13:49:03 -08:00
Igor Minar b370fac4fc $defer service should always call $eval after callback finished
Closes #189
2010-12-10 13:22:44 -08:00
Misko Hevery 23fc73081f Refactor lexer to use regular expressions 2010-12-08 14:39:22 -08:00
Misko Hevery e5e69d9b90 Remove RegExp parser
- RegExp parser is rearly used, feature, and one should not have RegExps
  in views anyways, so we are removing it

BACKWARD INCOMPATIBLE CHANGE!!!
2010-12-08 14:36:51 -08:00
Misko Hevery fa722447f8 Fixed failed assignments of form abj[0].name=value
Closes #169
2010-12-08 14:20:26 -08:00
Igor Minar 81d10e819e make the docs angular 'logo' link filename agnostic 2010-12-07 20:43:10 -08:00
Igor Minar 809ca94e1c @returns tag should allow the content to be split into multiple lines 2010-12-07 16:07:14 -08:00
Igor Minar 824eab9029 improving $resource docs 2010-12-07 16:06:31 -08:00
Igor Minar d503dfe99b preparations for the 0.9.7 sonic-scream iteration 2010-12-06 21:24:49 -08:00
16 changed files with 225 additions and 209 deletions
+18
View File
@@ -1,3 +1,21 @@
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
### Bug Fixes
- $defer service should always call $eval on the root scope after a callback runs (issue #189)
- fix for failed assignments of form obj[0].name=value (issue #169)
- significant parser improvements that resulted in lower memory usage
(commit 23fc73081feb640164615930b36ef185c23a3526)
### Docs
- small docs improvements (mainly docs for the $resource service)
### Breaking changes
- Angular expressions in the view used to support regular expressions. This feature was rarely
used and added unnecessary complexity. It not a good idea to have regexps in the view anyway,
so we removed this support. If you had any regexp in your views, you will have to move them to
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
# <angular/> 0.9.6 night-vision (2010-12-06) #
### Security
+1 -1
View File
@@ -208,7 +208,7 @@ function propertyTag(doc, name, value) {
}
function returnsTag(doc, name, value) {
var match = value.match(/^{(\S+)}\s+(.*)?/);
var match = value.match(/^{(\S+)}\s+([\s\S]*)?/);
if (match) {
var tag = {
+7 -8
View File
@@ -42,7 +42,7 @@ a {
#api-list {
position: absolute;
top: 3em;
bottom: 0;
bottom: 1em;
overflow-y: scroll;
padding-right: 0.8em;
}
@@ -225,25 +225,24 @@ a {
::-webkit-scrollbar{
width:0.8em;
height:0.8em;
border-left: 1px solid #ccc;
margin: 0.2em 0em;
}
::-webkit-scrollbar:hover{
background-color:#eee;
}
::-webkit-resizer{
-webkit-border-radius:0.3em;
background-color:#666;
}
::-webkit-scrollbar-thumb{
min-height:0.8em;
min-width:0.8em;
-webkit-border-radius:0.3em;
-webkit-border-radius:0.5em;
background-color: #ddd;
}
::-webkit-scrollbar-thumb:hover{
background-color: #bbb;
}
::-webkit-scrollbar-thumb:active{
background-color:#888;
}
+1 -1
View File
@@ -26,7 +26,7 @@
<div id="header">
<h1>
<span class="main-title">{{getTitle()}}</span>
<a href="index.html"><span class="angular">&lt;angular/&gt;</span> Docs</a>
<a href="#"><span class="angular">&lt;angular/&gt;</span> Docs</a>
</h1>
</div>
<div id="sidebar">
+6
View File
@@ -182,6 +182,12 @@ describe('collect', function(){
TAG.returns(doc, 'returns', '{string} descrip *tion*');
expect(doc.returns).toEqual({type: 'string', description: 'descrip <em>tion</em>'});
});
it('should support multiline content', function() {
TAG.returns(doc, 'returns', '{string} description\n new line\n another line');
expect(doc.returns).
toEqual({type: 'string', description: 'description\n new line\n another line'});
});
});
describe('@description', function(){
+1 -1
View File
@@ -699,7 +699,7 @@ function concat(array1, array2, index) {
*
* @param {Object} self Context in which `fn` should be evaluated in.
* @param {function()} fn Function to be bound.
* @param {(...*)=} args Optional arguments to be prebound to the `fn` function call.
* @param {...*} args Optional arguments to be prebound to the `fn` function call.
* @returns {function()} Function that wraps the `fn` with all the specified bindings.
*/
function bind(self, fn) {
+1 -1
View File
@@ -66,7 +66,7 @@ function getterFn(path){
code += 'if(!s) return s;\n' +
'l=s;\n' +
's=s' + key + ';\n' +
'if(typeof s=="function") s = function(){ return l'+key+'.apply(l, arguments); };\n';
'if(typeof s=="function" && !(s instanceof RegExp)) s = function(){ return l'+key+'.apply(l, arguments); };\n';
if (key.charAt(1) == '$') {
// special code for super-imposed functions
var name = key.substr(2);
+71 -138
View File
@@ -9,7 +9,7 @@ var OPERATORS = {
'/':function(self, a,b){return a/b;},
'%':function(self, a,b){return a%b;},
'^':function(self, a,b){return a^b;},
'=':function(self, a,b){return setter(self, a, b);},
'=':noop,
'==':function(self, a,b){return a==b;},
'!=':function(self, a,b){return a!=b;},
'<':function(self, a,b){return a<b;},
@@ -32,7 +32,7 @@ function lex(text, parseStringsForObjects){
index = 0,
json = [],
ch,
lastCh = ':'; // can start regexp
lastCh = ':';
while (index < text.length) {
ch = text.charAt(index);
@@ -40,8 +40,6 @@ function lex(text, parseStringsForObjects){
readString(ch);
} else if (isNumber(ch) || is('.') && isNumber(peek())) {
readNumber();
} else if ( was('({[:,;') && is('/') ) {
readRegexp();
} else if (isIdent(ch)) {
readIdent();
if (was('{,') && json[0]=='{' &&
@@ -73,6 +71,9 @@ function lex(text, parseStringsForObjects){
lastCh = ch;
}
return tokens;
//////////////////////////////////////////////
function is(chars) {
return chars.indexOf(ch) != -1;
@@ -97,10 +98,6 @@ function lex(text, parseStringsForObjects){
'A' <= ch && ch <= 'Z' ||
'_' == ch || ch == '$';
}
function isExpOperator(ch) {
return ch == '-' || ch == '+' || isNumber(ch);
}
function throwError(error, start, end) {
end = end || index;
throw Error("Lexer Error: " + error + " at column" +
@@ -109,128 +106,61 @@ function lex(text, parseStringsForObjects){
" " + end) +
" in expression [" + text + "].");
}
function consume(regexp, processToken, errorMsg) {
var match = text.substr(index).match(regexp);
var token = {index: index};
var start = index;
if (!match) throwError(errorMsg);
index += match[0].length;
processToken(token, token.text = match[0], start);
tokens.push(token);
}
function readNumber() {
var number = "";
var start = index;
while (index < text.length) {
var ch = lowercase(text.charAt(index));
if (ch == '.' || isNumber(ch)) {
number += ch;
} else {
var peekCh = peek();
if (ch == 'e' && isExpOperator(peekCh)) {
number += ch;
} else if (isExpOperator(ch) &&
peekCh && isNumber(peekCh) &&
number.charAt(number.length - 1) == 'e') {
number += ch;
} else if (isExpOperator(ch) &&
(!peekCh || !isNumber(peekCh)) &&
number.charAt(number.length - 1) == 'e') {
throwError('Invalid exponent');
} else {
break;
}
}
index++;
}
number = 1 * number;
tokens.push({index:start, text:number, json:true,
fn:function(){return number;}});
consume(/^(\d+)?(\.\d+)?([eE][+-]?\d+)?/, function(token, number){
token.text = number = 1 * number;
token.json = true;
token.fn = valueFn(number);
}, "Not a valid number");
}
function readIdent() {
var ident = "";
var start = index;
while (index < text.length) {
var ch = text.charAt(index);
if (ch == '.' || isIdent(ch) || isNumber(ch)) {
ident += ch;
} else {
break;
consume(/^[\w_\$][\w_\$\d]*(\.[\w_\$][\w_\$\d]*)*/, function(token, ident){
fn = OPERATORS[ident];
if (!fn) {
fn = getterFn(ident);
fn.isAssignable = ident;
}
index++;
}
var fn = OPERATORS[ident];
if (!fn) {
fn = getterFn(ident);
fn.isAssignable = ident;
}
tokens.push({index:start, text:ident, fn:fn, json: OPERATORS[ident]});
token.fn = OPERATORS[ident]||extend(getterFn(ident), {
assign:function(self, value){
return setter(self, ident, value);
}
});
token.json = OPERATORS[ident];
});
}
function readString(quote) {
var start = index;
index++;
var string = "";
var rawString = quote;
var escape = false;
while (index < text.length) {
var ch = text.charAt(index);
rawString += ch;
if (escape) {
if (ch == 'u') {
var hex = text.substring(index + 1, index + 5);
if (!hex.match(/[\da-f]{4}/i))
throwError( "Invalid unicode escape [\\u" + hex + "]");
index += 4;
string += String.fromCharCode(parseInt(hex, 16));
} else {
var rep = ESCAPE[ch];
if (rep) {
string += rep;
} else {
string += ch;
}
}
escape = false;
} else if (ch == '\\') {
escape = true;
} else if (ch == quote) {
index++;
tokens.push({index:start, text:rawString, string:string, json:true,
fn:function(){
return (string.length == dateParseLength) ?
angular['String']['toDate'](string) : string;
}});
return;
} else {
string += ch;
}
index++;
}
throwError("Unterminated quote", start);
}
function readRegexp(quote) {
var start = index;
index++;
var regexp = "";
var escape = false;
while (index < text.length) {
var ch = text.charAt(index);
if (escape) {
regexp += ch;
escape = false;
} else if (ch === '\\') {
regexp += ch;
escape = true;
} else if (ch === '/') {
index++;
var flags = "";
if (isIdent(text.charAt(index))) {
readIdent();
flags = tokens.pop().text;
}
var compiledRegexp = new RegExp(regexp, flags);
tokens.push({index:start, text:regexp, flags:flags,
fn:function(){return compiledRegexp;}});
return;
} else {
regexp += ch;
}
index++;
}
throwError("Unterminated RegExp", start);
consume(/^(('(\\'|[^'])*')|("(\\"|[^"])*"))/, function(token, rawString, start){
var hasError;
var string = token.string = rawString.substr(1, rawString.length - 2).
replace(/(\\u(.?.?.?.?))|(\\(.))/g,
function(match, wholeUnicode, unicode, wholeEscape, escape){
if (unicode && !unicode.match(/[\da-fA-F]{4}/))
hasError = hasError || bind(null, throwError, "Invalid unicode escape [\\u" + unicode + "]", start);
return unicode ?
String.fromCharCode(parseInt(unicode, 16)) :
ESCAPE[escape] || escape;
});
(hasError||noop)();
token.json = true;
token.fn = function(){
return (string.length == dateParseLength) ?
angular['String']['toDate'](string) :
string;
};
}, "Unterminated string");
}
}
@@ -384,14 +314,17 @@ function parser(text, json){
function assignment(){
var left = logicalOR();
var right;
var token;
if (token = expect('=')) {
if (!left.isAssignable) {
if (!left.assign) {
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());
right = logicalOR();
return function(self){
return left.assign(self, right(self));
};
} else {
return left;
}
@@ -518,28 +451,28 @@ function parser(text, json){
function fieldAccess(object) {
var field = expect().text;
var getter = getterFn(field);
var fn = function (self){
return extend(function (self){
return getter(object(self));
};
fn.isAssignable = field;
return fn;
}, {
assign:function(self, value){
return setter(object(self), field, value);
}
});
}
function objectIndex(obj) {
var indexFn = expression();
consume(']');
if (expect('=')) {
var rhs = expression();
return function (self){
return obj(self)[indexFn(self)] = rhs(self);
};
} else {
return function (self){
return extend(
function (self){
var o = obj(self);
var i = indexFn(self);
return (o) ? o[i] : _undefined;
};
}
}, {
assign:function(self, value){
return obj(self)[indexFn(self)] = value;
}
});
}
function functionCall(fn) {
+33 -15
View File
@@ -819,12 +819,16 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
* @param {function()} fn A function, who's execution should be deferred.
*/
angularServiceInject('$defer', function($browser, $exceptionHandler) {
var scope = this;
return function(fn) {
$browser.defer(function() {
try {
fn();
} catch(e) {
$exceptionHandler(e);
} finally {
scope.$eval();
}
});
};
@@ -888,7 +892,7 @@ angularServiceInject('$xhr.cache', function($xhr, $defer){
/**
* @workInProgress
* @ngdoc service
* @ngdoc function
* @name angular.service.$resource
* @requires $xhr
*
@@ -934,6 +938,33 @@ angularServiceInject('$xhr.cache', function($xhr, $defer){
expect(newCard.id).toEqual(789);
* </pre>
*
* The object returned from this function execution is a resource "class" which has "static" method
* for each action in the definition.
*
* Calling these methods invoke `$xhr` on the `url` template with the given `method` and `params`.
* When the data is returned from the server then the object is an instance of the resource type and
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
* operations (create, read, update, delete) on server-side data.
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function(){
user.abc = true;
user.$save();
});
</pre>
*
* It's worth noting that the callback for `get`, `query` and other method gets passed in the
* response that came from the server, so one could rewrite the above example as:
*
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
User.get({userId:123}, function(u){
u.abc = true;
u.$save();
});
</pre>
*
*
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
* `/user/:username`.
@@ -964,20 +995,7 @@ angularServiceInject('$xhr.cache', function($xhr, $defer){
'delete': {method:'DELETE'} };
* </pre>
*
* @returns {Object} A resource "class" which has "static" method for each action in the definition.
* Calling these methods invoke `$xhr` on the `url` template with the given `method` and
* `params`. When the data is returned from the server then the object is an instance of the
* resource type and all of the non-GET methods are available with `$` prefix. This allows you
* to easily support CRUD operations (create, read, update, delete) on server-side data.
<pre>
var User = $resource('/user/:userId', {userId:'@id'});
var user = User.get({userId:123}, function(){
user.abc = true;
user.$save();
});
</pre>
*
* @returns {Object} A resource "class".
*
* @example
<script>
+2 -1
View File
@@ -13,8 +13,9 @@ extend(angularValidator, {
* @css ng-validation-error
*
* @example
* <script> var ssn = /^\d\d\d-\d\d-\d\d\d\d$/; </script>
* Enter valid SSN:
* <input name="ssn" value="123-45-6789" ng:validate="regexp:/^\d\d\d-\d\d-\d\d\d\d$/" >
* <input name="ssn" value="123-45-6789" ng:validate="regexp:$window.ssn" >
*
* @scenario
* it('should invalidate non ssn', function(){
+16 -37
View File
@@ -59,14 +59,6 @@ describe('parser', function() {
expect(undefined).toEqual(tokens[i].fn());
});
it('should tokenize RegExp', function() {
var tokens = lex("/r 1/");
var i = 0;
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('r 1');
expect("r 1".match(tokens[i].fn())[0]).toEqual('r 1');
});
it('should tokenize quoted string', function() {
var str = "['\\'', \"\\\"\"]";
var tokens = lex(str);
@@ -90,26 +82,15 @@ describe('parser', function() {
expect(tokens.length).toEqual(1);
expect(tokens[0].string).toEqual('\u00a0');
});
it('should tokenize RegExp with options', function() {
var tokens = lex("/r/g");
var i = 0;
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('r');
expect(tokens[i].flags).toEqual('g');
expect("rr".match(tokens[i].fn()).length).toEqual(2);
});
it('should tokenize RegExp with escaping', function() {
var tokens = lex("/\\/\\d/");
var i = 0;
expect(tokens[i].index).toEqual(0);
expect(tokens[i].text).toEqual('\\/\\d');
expect("/1".match(tokens[i].fn())[0]).toEqual('/1');
it('should error when non terminated string', function(){
expect(function(){
lex('ignore "text');
}).toThrow(new Error('Lexer Error: Unterminated string at column 7 in expression [ignore "text].'));
});
it('should ignore whitespace', function() {
var tokens = lex("a \t \n \r b");
var tokens = lex("a \t \n \r \u00A0 b");
expect(tokens[0].text).toEqual('a');
expect(tokens[1].text).toEqual('b');
});
@@ -155,16 +136,6 @@ describe('parser', function() {
expect(tokens[0].text).toEqual(0.5E+10);
});
it('should throws exception for invalid exponent', function() {
expect(function() {
lex("0.5E-");
}).toThrow(new Error('Lexer Error: Invalid exponent at column 4 in expression [0.5E-].'));
expect(function() {
lex("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() {
var tokens = lex(".5");
expect(tokens[0].text).toEqual(0.5);
@@ -172,8 +143,8 @@ describe('parser', function() {
it('should throw error on invalid unicode', function() {
expect(function() {
lex("'\\u1''bla'");
}).toThrow(new Error("Lexer Error: Invalid unicode escape [\\u1''b] at column 2 in expression ['\\u1''bla']."));
lex("'\\u1xbla'");
}).toThrow(new Error("Lexer Error: Invalid unicode escape [\\u1xbl] at columns 0-9 ['\\u1xbla'] in expression ['\\u1xbla']."));
});
});
@@ -413,4 +384,12 @@ describe('parser', function() {
expect(scope.$eval("a=undefined")).not.toBeDefined();
expect(scope.$get("a")).not.toBeDefined();
});
it('should allow assignment after array dereference', function(){
scope = angular.scope();
scope.obj = [{}];
scope.$eval('obj[0].name=1');
expect(scope.obj.name).toBeUndefined();
expect(scope.obj[0].name).toEqual(1);
});
});
+5
View File
@@ -52,6 +52,11 @@ describe('scope/model', function(){
model.$eval('name="works"');
expect(model.name).toEqual('works');
});
it('should not bind regexps', function(){
model.exp = /abc/;
expect(model.$eval('exp')).toEqual(model.exp);
});
it('should do nothing on empty string and not update view', function(){
var onEval = jasmine.createSpy('onEval');
+49 -1
View File
@@ -361,12 +361,41 @@ describe("service", function(){
it('should delegate exception to the $exceptionHandler service', function() {
$defer(function() { throw "Test Error"; });
$defer(function() {throw "Test Error";});
expect($exceptionHandler).not.toHaveBeenCalled();
$browser.defer.flush();
expect($exceptionHandler).toHaveBeenCalledWith("Test Error");
});
it('should call eval after each callback is executed', function() {
var eval = this.spyOn(scope, '$eval').andCallThrough();
$defer(function() {});
expect(eval).wasNotCalled();
$browser.defer.flush();
expect(eval).wasCalled();
eval.reset(); //reset the spy;
$defer(function() {});
$defer(function() {});
$browser.defer.flush();
expect(eval.callCount).toBe(2);
});
it('should call eval even if an exception is thrown in callback', function() {
var eval = this.spyOn(scope, '$eval').andCallThrough();
$defer(function() {throw "Test Error"});
expect(eval).wasNotCalled();
$browser.defer.flush();
expect(eval).wasCalled();
});
});
@@ -543,6 +572,25 @@ describe("service", function(){
$browser.defer.flush();
expect(log).toEqual('"+";"+";'); //callback has executed
});
it('should call eval after callbacks for both cache hit and cache miss execute', function() {
var eval = this.spyOn(scope, '$eval').andCallThrough();
$browserXhr.expectGET('/url').respond('+');
cache('GET', '/url', null, callback);
expect(eval).wasNotCalled();
$browserXhr.flush();
expect(eval).wasCalled();
eval.reset(); //reset the spy
cache('GET', '/url', null, callback);
expect(eval).wasNotCalled();
$browser.defer.flush();
expect(eval).wasCalled();
})
});
});
+2
View File
@@ -13,6 +13,8 @@ if (window.jstestdriver) {
}
beforeEach(function(){
// This is to reset parsers global cache of expressions.
compileCache = {};
this.addMatchers({
toBeInvalid: function(){
var element = jqLite(this.actual);
+10 -3
View File
@@ -41,6 +41,13 @@ describe("widget", function(){
expect(scope.$get('name')).toEqual('Kai');
expect(scope.$get('count')).toEqual(2);
});
it('should allow complex refernce binding', function(){
compile('<div ng:init="obj={abc:{}}">'+
'<input type="Text" name="obj[\'abc\'].name" value="Misko""/>'+
'</div>');
expect(scope.obj['abc'].name).toEqual('Misko');
});
describe("ng:format", function(){
@@ -564,8 +571,8 @@ describe("widget", function(){
// this one should really be just '1', but due to lack of real events things are not working
// properly. see discussion at: http://is.gd/ighKk
expect(element.text()).toEqual('2');
dealoc(scope);
expect(element.text()).toEqual('4');
dealoc(element);
});
it('should evaluate onload expression when a partial is loaded', function() {
@@ -580,7 +587,7 @@ describe("widget", function(){
scope.$inject('$browser').defer.flush();
expect(element.text()).toEqual('my partial');
expect(scope.loaded).toBe(true);
dealoc(scope);
dealoc(element);
});
});
+2 -2
View File
@@ -1,4 +1,4 @@
# <angular/> build config file
---
version: 0.9.6
codename: night-vision
version: 0.9.7
codename: sonic-scream