aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Angular.js28
-rw-r--r--src/Browser.js2
-rw-r--r--src/Parser.js21
-rw-r--r--src/Scope.js74
-rw-r--r--src/angular-bootstrap.js2
-rw-r--r--src/apis.js10
-rw-r--r--src/directives.js62
-rw-r--r--src/widgets.js15
8 files changed, 128 insertions, 86 deletions
diff --git a/src/Angular.js b/src/Angular.js
index 8675bc40..2b26c88d 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -244,18 +244,17 @@ function copy(source, destination){
while(destination.length) {
destination.pop();
}
+ for ( var i = 0; i < source.length; i++) {
+ destination.push(copy(source[i]));
+ }
} else {
foreach(destination, function(value, key){
delete destination[key];
});
+ for ( var key in source) {
+ destination[key] = copy(source[key]);
+ }
}
- foreach(source, function(value, key){
- destination[key] = value ?
- ( isArray(value) ?
- copy(value, []) :
- (isObject(value) ? copy(value, {}) : value)) :
- value;
- });
return destination;
}
}
@@ -291,12 +290,15 @@ function elementError(element, type, error) {
while (!isRenderableElement(element)) {
element = element.parent() || jqLite(document.body);
}
- if (error) {
- element.addClass(type);
- element.attr(type, error);
- } else {
- element.removeClass(type);
- element.removeAttr(type);
+ if (element[0]['$NG_ERROR'] !== error) {
+ element[0]['$NG_ERROR'] = error;
+ if (error) {
+ element.addClass(type);
+ element.attr(type, error);
+ } else {
+ element.removeClass(type);
+ element.removeAttr(type);
+ }
}
}
diff --git a/src/Browser.js b/src/Browser.js
index 0e265c0c..0552b3ae 100644
--- a/src/Browser.js
+++ b/src/Browser.js
@@ -3,7 +3,7 @@
//////////////////////////////
function Browser(location, document) {
- this.delay = 25;
+ this.delay = 50;
this.expectedUrl = location.href;
this.urlListeners = [];
this.hoverListener = noop;
diff --git a/src/Parser.js b/src/Parser.js
index dfe56cc9..df270792 100644
--- a/src/Parser.js
+++ b/src/Parser.js
@@ -17,7 +17,7 @@ Lexer.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 self.scope.set(a, b);},
+ '=':function(self, a,b){return setter(self, a, b);},
'==':function(self, a,b){return a==b;},
'!=':function(self, a,b){return a!=b;},
'<':function(self, a,b){return a<b;},
@@ -151,9 +151,7 @@ Lexer.prototype = {
}
var fn = Lexer.OPERATORS[ident];
if (!fn) {
- fn = function(self){
- return self.scope.get(ident);
- };
+ fn = getterFn(ident);
fn.isAssignable = ident;
}
this.tokens.push({index:start, text:ident, fn:fn});
@@ -372,7 +370,7 @@ Parser.prototype = {
for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self));
}
- return fn.apply(self.state, args);
+ return fn.apply(self, args);
};
return function(){
return fnInvoke;
@@ -551,20 +549,21 @@ Parser.prototype = {
this.consume("}");
return function(self) {
return function($){
- var scope = createScope(self.state);
+ var scope = createScope(self);
scope['$'] = $;
for ( var i = 0; i < args.length; i++) {
- scope.$set(args[i], arguments[i]);
+ setter(scope, args[i], arguments[i]);
}
- return statements({scope:{get:scope.$get, set:scope.$set}});
+ return statements(scope);
};
};
},
fieldAccess: function(object) {
var field = this.expect().text;
+ var getter = getterFn(field);
var fn = function (self){
- return getter(object(self), field);
+ return getter(object(self));
};
fn.isAssignable = field;
return fn;
@@ -680,11 +679,11 @@ Parser.prototype = {
}
return function(self) {
var Entity = self.datastore.entity(entity, defaults);
- self.scope.set(entity, Entity);
+ setter(self, entity, Entity);
if (instance) {
var document = Entity();
document['$$anchor'] = instance;
- self.scope.set(instance, document);
+ setter(self, instance, document);
return "$anchor." + instance + ":{" +
instance + "=" + entity + ".load($anchor." + instance + ");" +
instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" +
diff --git a/src/Scope.js b/src/Scope.js
index fe0b6ce3..637fc25e 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -43,31 +43,55 @@ function setter(instance, path, value){
return value;
}
+///////////////////////////////////
+
+var getterFnCache = {};
+function getterFn(path){
+ var fn = getterFnCache[path];
+ if (fn) return fn;
+
+ var code = 'function (self){\n';
+ code += ' var last, fn, type;\n';
+ foreach(path.split('.'), function(key) {
+ key = (key == 'this') ? '["this"]' : '.' + key;
+ code += ' if(!self) return self;\n';
+ code += ' last = self;\n';
+ code += ' self = self' + key + ';\n';
+ code += ' if(typeof self == "function") \n';
+ code += ' self = function(){ return last'+key+'.apply(last, arguments); };\n';
+ if (key.charAt(1) == '$') {
+ // special code for super-imposed functions
+ var name = key.substr(2);
+ code += ' if(!self) {\n';
+ code += ' type = angular.Global.typeOf(last);\n';
+ code += ' fn = (angular[type.charAt(0).toUpperCase() + type.substring(1)]||{})["' + name + '"];\n';
+ code += ' if (fn)\n';
+ code += ' self = function(){ return fn.apply(last, [last].concat(slice.call(arguments, 0, arguments.length))); };\n';
+ code += ' }\n';
+ }
+ });
+ code += ' return self;\n}';
+ fn = eval('(' + code + ')');
+ fn.toString = function(){ return code; };
+
+ return getterFnCache[path] = fn;
+}
+
+///////////////////////////////////
+
var compileCache = {};
function expressionCompile(exp){
if (isFunction(exp)) return exp;
- var expFn = compileCache[exp];
- if (!expFn) {
+ var fn = compileCache[exp];
+ if (!fn) {
var parser = new Parser(exp);
- expFn = parser.statements();
+ var fnSelf = parser.statements();
parser.assertAllConsumed();
- compileCache[exp] = expFn;
+ fn = compileCache[exp] = extend(
+ function(){ return fnSelf(this);},
+ {fnSelf: fnSelf});
}
- return parserNewScopeAdapter(expFn);
-}
-
-// return expFn
-// TODO(remove this hack)
-function parserNewScopeAdapter(fn) {
- return function(){
- return fn({
- state: this,
- scope: {
- set: this.$set,
- get: this.$get
- }
- });
- };
+ return fn;
}
function rethrow(e) { throw e; }
@@ -100,11 +124,13 @@ function createScope(parent, services, existing) {
if (exp !== undefined) {
return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length));
} else {
- foreach(evalLists.sorted, function(list) {
- foreach(list, function(eval) {
- instance.$tryEval(eval.fn, eval.handler);
- });
- });
+ for ( var i = 0, iSize = evalLists.sorted.length; i < iSize; i++) {
+ for ( var queue = evalLists.sorted[i],
+ jSize = queue.length,
+ j= 0; j < jSize; j++) {
+ instance.$tryEval(queue[j].fn, queue[j].handler);
+ }
+ }
}
},
diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js
index 704c50e2..90e1104e 100644
--- a/src/angular-bootstrap.js
+++ b/src/angular-bootstrap.js
@@ -50,6 +50,7 @@
addScript("/AngularPublic.js");
// Extension points
+ addScript("/services.js");
addScript("/apis.js");
addScript("/filters.js");
addScript("/formatters.js");
@@ -57,7 +58,6 @@
addScript("/directives.js");
addScript("/markups.js");
addScript("/widgets.js");
- addScript("/services.js");
window.onload = function(){
try {
diff --git a/src/apis.js b/src/apis.js
index 5864ad60..306d0ce8 100644
--- a/src/apis.js
+++ b/src/apis.js
@@ -178,7 +178,7 @@ var angularArray = {
descending = $.charAt(0) == '-';
$ = $.substring(1);
}
- var get = $ ? angular['Function']['compile']($) : identity;
+ var get = $ ? expressionCompile($).fnSelf : identity;
return reverse(function(a,b){
return compare(get(a),get(b));
}, descending);
@@ -190,7 +190,9 @@ var angularArray = {
}
return 0;
};
- return copy(array).sort(reverse(comparator, descend));
+ var arrayCopy = [];
+ for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
+ return arrayCopy.sort(reverse(comparator, descend));
},
'orderByToggle':function(predicate, attribute) {
var STRIP = /^([+|-])?(.*)/;
@@ -296,9 +298,7 @@ var angularFunction = {
if (isFunction(expression)){
return expression;
} else if (expression){
- return function($) {
- return createScope($).$eval(expression);
- };
+ return expressionCompile(expression).fnSelf;
} else {
return identity;
}
diff --git a/src/directives.js b/src/directives.js
index a3575d62..cabf0c23 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -24,14 +24,17 @@ angularDirective("ng-eval", function(expression){
angularDirective("ng-bind", function(expression){
return function(element) {
- var lastValue, lastError;
+ var lastValue = noop, lastError = noop;
this.$onEval(function() {
var error,
value = this.$tryEval(expression, function(e){
error = toJson(e);
}),
- isHtml = value instanceof HTML,
- isDomElement = isElement(value);
+ isHtml,
+ isDomElement;
+ if (lastValue === value && lastError == error) return;
+ isHtml = value instanceof HTML,
+ isDomElement = isElement(value);
if (!isHtml && !isDomElement && isObject(value)) {
value = toJson(value);
}
@@ -72,14 +75,14 @@ function compileBindTemplate(template){
});
bindTemplateCache[template] = fn = function(element){
var parts = [], self = this;
- foreach(bindings, function(fn){
- var value = fn.call(self, element);
+ for ( var i = 0; i < bindings.length; i++) {
+ var value = bindings[i].call(self, element);
if (isElement(value))
value = '';
else if (isObject(value))
value = toJson(value, true);
parts.push(value);
- });
+ };
return parts.join('');
};
}
@@ -107,21 +110,26 @@ var REMOVE_ATTRIBUTES = {
};
angularDirective("ng-bind-attr", function(expression){
return function(element){
+ var lastValue = {};
this.$onEval(function(){
- foreach(this.$eval(expression), function(bindExp, key) {
- var value = compileBindTemplate(bindExp).call(this, element),
+ var values = this.$eval(expression);
+ for(var key in values) {
+ var value = compileBindTemplate(values[key]).call(this, element),
specialName = REMOVE_ATTRIBUTES[lowercase(key)];
- if (specialName) {
- if (element[specialName] = toBoolean(value)) {
- element.attr(specialName, value);
+ if (lastValue[key] !== value) {
+ lastValue[key] = value;
+ if (specialName) {
+ if (element[specialName] = toBoolean(value)) {
+ element.attr(specialName, value);
+ } else {
+ element.removeAttr(key);
+ }
+ (element.data('$validate')||noop)();
} else {
- element.removeAttr(key);
+ element.attr(key, value);
}
- (element.data('$validate')||noop)();
- } else {
- element.attr(key, value);
}
- }, this);
+ };
}, element);
};
});
@@ -153,18 +161,19 @@ angularWidget("@ng-repeat", function(expression, element){
var children = [], currentScope = this;
this.$onEval(function(){
- var index = 0, childCount = children.length, childScope, lastElement = reference;
- foreach(this.$tryEval(rhs, reference), function(value, key){
- function assign(scope) {
- scope[valueIdent] = value;
- if (keyIdent) scope[keyIdent] = key;
- }
+ var index = 0, childCount = children.length, childScope, lastElement = reference,
+ collection = this.$tryEval(rhs, reference);
+ for ( var key in collection) {
if (index < childCount) {
// reuse existing child
- assign(childScope = children[index]);
+ childScope = children[index];
+ childScope[valueIdent] = collection[key];
+ if (keyIdent) childScope[keyIdent] = key;
} else {
// grow children
- assign(childScope = template(element.clone(), createScope(currentScope)));
+ childScope = template(element.clone(), createScope(currentScope));
+ childScope[valueIdent] = collection[key];
+ if (keyIdent) childScope[keyIdent] = key;
lastElement.after(childScope.$element);
childScope.$index = index;
childScope.$element.attr('ng-repeat-index', index);
@@ -174,7 +183,7 @@ angularWidget("@ng-repeat", function(expression, element){
childScope.$eval();
lastElement = childScope.$element;
index ++;
- });
+ };
// shrink children
while(children.length > index) {
children.pop().$element.remove();
@@ -198,10 +207,9 @@ angularDirective("ng-watch", function(expression, element){
return function(element){
var self = this;
new Parser(expression).watch()({
- scope:{get: self.$get, set: self.$set},
addListener:function(watch, exp){
self.$watch(watch, function(){
- return exp({scope:{get: self.$get, set: self.$set}, state:self});
+ return exp(self);
}, element);
}
});
diff --git a/src/widgets.js b/src/widgets.js
index 43bed81f..5df92de0 100644
--- a/src/widgets.js
+++ b/src/widgets.js
@@ -35,16 +35,23 @@ function compileValidator(expr) {
function valueAccessor(scope, element) {
var validatorName = element.attr('ng-validate') || NOOP,
validator = compileValidator(validatorName),
- required = element.attr('ng-required'),
+ requiredExpr = element.attr('ng-required'),
farmatterName = element.attr('ng-format') || NOOP,
formatter = angularFormatter(farmatterName),
- format, parse, lastError;
+ format, parse, lastError, required;
invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop};
if (!validator) throw "Validator named '" + validatorName + "' not found.";
if (!formatter) throw "Formatter named '" + farmatterName + "' not found.";
format = formatter.format;
parse = formatter.parse;
- required = required || required === '';
+ if (requiredExpr) {
+ scope.$watch(requiredExpr, function(newValue) {
+ required = newValue;
+ validate();
+ });
+ } else {
+ required = requiredExpr === '';
+ }
element.data('$validate', validate);
return {
@@ -80,7 +87,7 @@ function valueAccessor(scope, element) {
validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element});
error = required && !value ?
"Required" :
- (value ? validator({state:validateScope, scope:{get:validateScope.$get, set:validateScope.$set}}, value) : null);
+ (value ? validator(validateScope, value) : null);
elementError(element, NG_VALIDATION_ERROR, error);
lastError = error;
if (error) {