diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Angular.js | 28 | ||||
| -rw-r--r-- | src/Browser.js | 2 | ||||
| -rw-r--r-- | src/Parser.js | 21 | ||||
| -rw-r--r-- | src/Scope.js | 74 | ||||
| -rw-r--r-- | src/angular-bootstrap.js | 2 | ||||
| -rw-r--r-- | src/apis.js | 10 | ||||
| -rw-r--r-- | src/directives.js | 62 | ||||
| -rw-r--r-- | src/widgets.js | 15 |
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) { |
