Merge branch 'master' of github.com:angular/angular.js

This commit is contained in:
Misko Hevery 2010-05-10 10:36:06 -07:00
commit f5027cc375
19 changed files with 167 additions and 64 deletions

View file

@ -231,13 +231,14 @@ function isLeafNode (node) {
function copy(source, destination){
if (!destination) {
if (isArray(source)) {
return copy(source, []);
} else if (isObject(source)) {
return copy(source, {});
} else {
return source;
if (source) {
if (isArray(source)) {
return copy(source, []);
} else if (isObject(source)) {
return copy(source, {});
}
}
return source;
} else {
if (isArray(source)) {
while(destination.length) {
@ -249,7 +250,11 @@ function copy(source, destination){
});
}
foreach(source, function(value, key){
destination[key] = isArray(value) ? copy(value, []) : (isObject(value) ? copy(value, {}) : value);
destination[key] = value ?
( isArray(value) ?
copy(value, []) :
(isObject(value) ? copy(value, {}) : value)) :
value;
});
return destination;
}

View file

@ -22,6 +22,7 @@ extend(angular, {
'isDefined': isDefined,
'isString': isString,
'isFunction': isFunction,
'isObject': isObject,
'isNumber': isNumber,
'isArray': isArray
});

View file

@ -8,6 +8,7 @@ function Browser(location, document) {
this.expectedUrl = location.href;
this.urlListeners = [];
this.hoverListener = noop;
this.isMock = false;
this.XHR = window.XMLHttpRequest || function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
@ -53,6 +54,10 @@ Browser.prototype = {
},
xhr: function(method, url, post, callback){
if (isFunction(post)) {
callback = post;
post = null;
}
var xhr = new this.XHR();
xhr.open(method, url, true);
xhr.onreadystatechange = function() {
@ -60,7 +65,7 @@ Browser.prototype = {
callback(xhr.status || 200, xhr.responseText);
}
};
xhr.send('');
xhr.send(post || '');
},
watchUrl: function(fn){

View file

@ -77,7 +77,18 @@ function Compiler(textMarkup, attrMarkup, directives, widgets){
Compiler.prototype = {
compile: function(rawElement) {
rawElement = jqLite(rawElement);
var template = this.templatize(rawElement, 0, 0) || new Template();
var index = 0,
template,
parent = rawElement.parent();
if (parent && parent[0]) {
parent = parent[0];
for(var i = 0; i < parent.childNodes.length; i++) {
if (parent.childNodes[i] == rawElement[0]) {
index = i;
}
}
}
template = this.templatize(rawElement, index, 0) || new Template();
return function(element, parentScope){
element = jqLite(element);
var scope = parentScope && parentScope.$eval ?

View file

@ -74,7 +74,7 @@ function toJsonArray(buf, obj, pretty, stack){
var childPretty = pretty ? pretty + " " : false;
var keys = [];
for(var k in obj) {
if (k.indexOf('$$') === 0)
if (k.indexOf('$$') === 0 || obj[k] === undefined)
continue;
keys.push(k);
}

View file

@ -21,7 +21,7 @@ Route.prototype = {
});
url = url.replace(/\/?#$/, '');
var query = [];
foreach(params, function(value, key){
foreachSorted(params, function(value, key){
if (!self.urlParams[key]) {
query.push(encodeURI(key) + '=' + encodeURI(value));
}
@ -69,14 +69,18 @@ ResourceFactory.prototype = {
switch(arguments.length) {
case 3: callback = a3;
case 2:
if (typeof a2 == 'function') {
if (isFunction(a2)) {
callback = a2;
} else {
params = a1;
data = a2;
break;
}
case 1: if (isPost) data = a1; else params = a1; break;
case 1:
if (isFunction(a1)) callback = a1;
else if (isPost) data = a1;
else params = a1;
break;
case 0: break;
default:
throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments.";
@ -109,7 +113,7 @@ ResourceFactory.prototype = {
case 1: if (typeof a1 == 'function') callback = a1; else params = a1;
case 0: break;
default:
throw "Expected between 1-3 arguments [params, data, callback], got " + arguments.length + " arguments.";
throw "Expected between 1-2 arguments [params, callback], got " + arguments.length + " arguments.";
}
var self = this;
Resource[name](params, this, function(response){

View file

@ -97,7 +97,7 @@ function createScope(parent, services, existing) {
$set: bind(instance, setter, instance),
$eval: function $eval(exp) {
if (isDefined(exp)) {
if (exp) {
return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length));
} else {
foreach(evalLists.sorted, function(list) {
@ -127,10 +127,11 @@ function createScope(parent, services, existing) {
var watch = expressionCompile(watchExp),
last;
function watcher(){
var value = watch.call(instance);
var value = watch.call(instance),
lastValue = last;
if (last !== value) {
instance.$tryEval(listener, exceptionHandler, value, last);
last = value;
instance.$tryEval(listener, exceptionHandler, value, lastValue);
}
}
instance.$onEval(PRIORITY_WATCH, watcher);

View file

@ -30,8 +30,9 @@ angularDirective("ng-bind", function(expression){
value = this.$tryEval(expression, function(e){
error = toJson(e);
}),
isHtml = value instanceof HTML;
if (!isHtml && isObject(value)) {
isHtml = value instanceof HTML,
isDomElement = isElement(value);
if (!isHtml && !isDomElement && isObject(value)) {
value = toJson(value);
}
if (value != lastValue || error != lastError) {
@ -41,6 +42,9 @@ angularDirective("ng-bind", function(expression){
if (error) value = error;
if (isHtml) {
element.html(value.html);
} else if (isDomElement) {
element.html('');
element.append(value);
} else {
element.text(value);
}

View file

@ -64,6 +64,12 @@ angularService("$location", function(browser){
return location;
}, {inject: ['$browser']});
angularService("$log", function(){
return {
error: noop
};
});
angularService("$hover", function(browser) {
var tooltip, self = this, error, width = 300, arrowWidth = 10;
browser.hover(function(element, show){
@ -103,7 +109,7 @@ angularService("$hover", function(browser) {
width: width + "px"
});
}
} else if (tooltip && false) {
} else if (tooltip) {
tooltip.callout.remove();
tooltip = null;
}
@ -152,6 +158,7 @@ angularService('$route', function(location, params){
onChange = [],
matcher = angularWidget('NG:SWITCH').route,
parentScope = this,
dirty = 0,
$route = {
routes: routes,
onChange: bind(onChange, onChange.push),
@ -160,7 +167,7 @@ angularService('$route', function(location, params){
var route = routes[path];
if (!route) route = routes[path] = {};
if (params) angular.extend(route, params);
if (matcher(location.hashPath, path)) updateRoute();
dirty++;
return route;
}
};
@ -185,13 +192,17 @@ angularService('$route', function(location, params){
parentScope.$tryEval(childScope.init);
}
}
this.$watch(function(){return location.hash;}, updateRoute);
this.$watch(function(){return dirty + location.hash;}, updateRoute);
return $route;
}, {inject: ['$location']});
angularService('$xhr', function($browser){
var self = this;
return function(method, url, post, callback){
if (isFunction(post)) {
callback = post;
post = null;
}
if (post && isObject(post)) {
post = toJson(post);
}
@ -213,33 +224,58 @@ angularService('$xhr.bulk', function($xhr){
callbacks = [],
scope = this;
function bulkXHR(method, url, post, callback) {
requests.push({method: method, url: url, data:post});
callbacks.push(callback);
}
bulkXHR.url = "/bulk";
bulkXHR.flush = function(callback){
var currentRequests = requests,
currentCallbacks = callbacks;
requests = [];
callbacks = [];
$xhr('POST', bulkXHR.url, {requests:currentRequests}, function(code, response){
foreach(response, function(response, i){
try {
(currentCallbacks[i] || noop)(response.status, response.response);
} catch(e) {
self.$log.error(e);
}
});
(callback || noop)();
if (isFunction(post)) {
callback = post;
post = null;
}
var currentQueue;
foreach(bulkXHR.urls, function(queue){
if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) {
currentQueue = queue;
}
});
if (currentQueue) {
if (!currentQueue.requests) currentQueue.requests = [];
if (!currentQueue.callbacks) currentQueue.callbacks = [];
currentQueue.requests.push({method: method, url: url, data:post});
currentQueue.callbacks.push(callback);
} else {
$xhr(method, url, post, callback);
}
}
bulkXHR.urls = {};
bulkXHR.flush = function(callback){
foreach(bulkXHR.urls, function(queue, url){
var currentRequests = queue.requests,
currentCallbacks = queue.callbacks;
if (currentRequests && currentRequests.length) {
queue.requests = [];
queue.callbacks = [];
$xhr('POST', url, {requests:currentRequests}, function(code, response){
foreach(response, function(response, i){
try {
(currentCallbacks[i] || noop)(response.status, response.response);
} catch(e) {
scope.$log.error(e);
}
});
(callback || noop)();
});
scope.$eval();
}
});
scope.$eval();
};
this.$onEval(PRIORITY_LAST, bulkXHR.flush);
return bulkXHR;
}, {inject:['$xhr']});
angularService('$xhr.cache', function($xhr){
var inflight = {};
var inflight = {}, self = this;;
function cache(method, url, post, callback){
if (isFunction(post)) {
callback = post;
post = null;
}
if (method == 'GET') {
var data;
if (data = cache.data[url]) {
@ -251,14 +287,15 @@ angularService('$xhr.cache', function($xhr){
cache.delegate(method, url, post, function(status, response){
if (status == 200)
cache.data[url] = { value: response };
foreach(inflight[url].callbacks, function(callback){
var callbacks = inflight[url].callbacks;
delete inflight[url];
foreach(callbacks, function(callback){
try {
(callback||noop)(status, copy(response));
} catch(e) {
self.$log.error(e);
}
});
delete inflight[url];
});
}
} else {
@ -269,7 +306,7 @@ angularService('$xhr.cache', function($xhr){
cache.data = {};
cache.delegate = $xhr;
return cache;
}, {inject:['$xhr']});
}, {inject:['$xhr.bulk']});
angularService('$resource', function($xhr){
var resource = new ResourceFactory($xhr);

View file

@ -192,22 +192,21 @@ angularWidget('NG:INCLUDE', function(element){
function incrementChange(){ changeCounter++;}
this.$watch(srcExp, incrementChange);
this.$watch(scopeExp, incrementChange);
scope.$onEval(function(){
if (childScope) childScope.$eval();
});
this.$watch(function(){return changeCounter;}, function(){
var src = this.$eval(srcExp),
useScope = this.$eval(scopeExp);
if (src) {
scope.$browser.xhr('GET', src, function(code, response){
scope.$xhr.cache('GET', src, function(code, response){
element.html(response);
childScope = useScope || createScope(scope);
compiler.compile(element)(element, childScope);
childScope.$init();
scope.$root.$eval();
});
}
});
scope.$onEval(function(){
if (childScope) childScope.$eval();
});
};
}
});

View file

@ -1,2 +1,2 @@
# java -jar lib/jstestdriver/JsTestDriver.jar --tests all
java -jar lib/jstestdriver/JsTestDriver.jar --tests all --config jsTestDriver-jquery.conf
java -jar lib/jstestdriver/JsTestDriver.jar --tests all
# java -jar lib/jstestdriver/JsTestDriver.jar --tests all --config jsTestDriver-jquery.conf

View file

@ -41,4 +41,12 @@ describe("copy", function(){
assertEquals(src.a, dst.a);
assertNotSame(src.a, dst.a);
});
it("should copy primitives", function(){
expect(copy(null)).toEqual(null);
expect(copy('')).toEqual('');
expect(copy(123)).toEqual(123);
expect(copy([{key:null}])).toEqual([{key:null}]);
});
});

View file

@ -63,9 +63,9 @@ JsonTest.prototype.testItShouldEscapeUnicode = function () {
JsonTest.prototype.testItShouldUTCDates = function() {
var date = angular.String.toDate("2009-10-09T01:02:03Z");
assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
assertEquals(date.getTime(),
fromJson('"2009-10-09T01:02:03Z"').getTime());
assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
assertEquals(date.getTime(),
fromJson('"2009-10-09T01:02:03Z"').getTime());
};
JsonTest.prototype.testItShouldPreventRecursion = function () {
@ -78,3 +78,7 @@ JsonTest.prototype.testItShouldSerializeSameObjectsMultipleTimes = function () {
var obj = {a:'b'};
assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj}));
};
JsonTest.prototype.testItShouldNotSerializeUndefinedValues = function () {
assertEquals('{}', angular.toJson({A:undefined}));
};

View file

@ -81,6 +81,15 @@ describe("resource", function() {
expect(callback).wasCalledWith(ccs);
});
it("should have all arguments optional", function(){
xhr.expectGET('/CreditCard').respond([{id:1}]);
var log = '';
var ccs = CreditCard.query(function(){ log += 'cb;'; });
xhr.flush();
nakedExpect(ccs).toEqual([{id:1}]);
expect(log).toEqual('cb;');
});
it('should delete resource', function(){
xhr.expectDELETE("/CreditCard/123").respond({});

11
test/angular-mocks.js vendored
View file

@ -26,16 +26,17 @@ function MockBrowser() {
var self = this,
expectations = {},
requests = [];
this.isMock = true;
self.url = "http://server";
self.watches = [];
self.xhr = function(method, url, data, callback) {
if (isFunction(data)) {
if (angular.isFunction(data)) {
callback = data;
data = null;
}
if (data && isObject(data)) data = angular.toJson(data);
if (data && isString(data)) url += "|" + data;
if (data && angular.isObject(data)) data = angular.toJson(data);
if (data && angular.isString(data)) url += "|" + data;
var expect = expectations[method] || {};
var response = expect[url];
if (!response) {
@ -48,8 +49,8 @@ function MockBrowser() {
self.xhr.expectations = expectations;
self.xhr.requests = requests;
self.xhr.expect = function(method, url, data) {
if (data && isObject(data)) data = angular.toJson(data);
if (data && isString(data)) url += "|" + data;
if (data && angular.isObject(data)) data = angular.toJson(data);
if (data && angular.isString(data)) url += "|" + data;
var expect = expectations[method] || (expectations[method] = {});
return {
respond: function(response) {

View file

@ -44,6 +44,15 @@ describe("directives", function(){
expect(lowercase(element.html())).toEqual('<div>hello</div>');
});
it('should ng-bind element', function() {
angularFilter.myElement = function() {
return jqLite('<a>hello</a>');
};
var scope = compile('<div ng-bind="0|myElement"></div>');
scope.$eval();
expect(lowercase(element.html())).toEqual('<a>hello</a>');
});
it('should ng-bind-template', function() {
var scope = compile('<div ng-bind-template="Hello {{name}}!"></div>');
scope.$set('name', 'Misko');

View file

@ -149,6 +149,7 @@ describe("service", function(){
expect(scope.$route.current).toEqual(null);
scope.$route.when('/NONE', {template:'instant update'});
scope.$eval();
expect(scope.$route.current.template).toEqual('instant update');
});
});
@ -187,7 +188,7 @@ describe("service", function(){
describe('bulk', function(){
it('should collect requests', function(){
scope.$xhr.bulk.url = "/";
scope.$xhr.bulk.urls["/"] = {match:/.*/};
scope.$xhr.bulk('GET', '/req1', null, callback);
scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback);
@ -225,7 +226,11 @@ describe("service", function(){
});
it('should keep track of in flight requests and request only once', function(){
cache.delegate = scope.$xhr.bulk;
scope.$xhr.bulk.urls['/bulk'] = {
match:function(url){
return url == '/url';
}
};
xhr.expectPOST('/bulk', {
requests:[{method:'GET', url:'/url', data: null}]
}).respond([

View file

@ -19,6 +19,7 @@ extend(angular, {
'identity':identity,
'isUndefined': isUndefined,
'isDefined': isDefined,
'isObject': isObject,
'isString': isString,
'isFunction': isFunction,
'isNumber': isNumber,

View file

@ -276,9 +276,8 @@ describe("widget", function(){
scope.childScope = createScope();
scope.childScope.name = 'misko';
scope.url = 'myUrl';
scope.$browser.xhr.expect('GET', 'myUrl').respond('{{name}}');
scope.$xhr.cache.data.myUrl = {value:'{{name}}'};
scope.$init();
scope.$browser.xhr.flush();
expect(element.text()).toEqual('misko');
});
});