aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Angular.js21
-rw-r--r--src/Compiler.js17
-rw-r--r--src/Scope.js192
-rw-r--r--src/directives.js120
-rw-r--r--src/widgets2.js2
5 files changed, 245 insertions, 107 deletions
diff --git a/src/Angular.js b/src/Angular.js
index 0c6d081e..0cb89bbe 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -208,27 +208,6 @@ function bind(_this, _function) {
};
}
-function bindTry(_this, _function) {
- var args = arguments,
- last = args.length - 1,
- curryArgs = slice.call(args, 2, last),
- exceptionHandler = args[last];
- return function() {
- try {
- return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length)));
- } catch (e) {
- if (e = exceptionHandler(e)) throw e;
- }
- };
-}
-
-function errorHandlerFor(element) {
- return function(error){
- element.attr('ng-error', angular.toJson(error));
- element.addClass('ng-exception');
- };
-}
-
function outerHTML(node) {
var temp = document.createElement('div');
temp.appendChild(node);
diff --git a/src/Compiler.js b/src/Compiler.js
index 47ab0c14..3b492ebe 100644
--- a/src/Compiler.js
+++ b/src/Compiler.js
@@ -14,7 +14,7 @@ Template.prototype = {
init: function(element, scope) {
element = jqLite(element);
foreach(this.inits, function(fn) {
- scope.apply(fn, element);
+ scope.$tryEval(fn, element, element);
});
var i,
@@ -92,14 +92,11 @@ Compiler.prototype = {
rawElement = jqLite(rawElement);
var template = this.templatize(rawElement) || new Template();
return function(element, parentScope){
- var scope = new Scope(parentScope);
- scope.element = element;
- // todo return should be a scope with everything already set on it as element
- return {
- scope: scope,
- element:element,
- init: bind(template, template.init, element, scope)
- };
+ var model = scope(parentScope);
+ return extend(model, {
+ $element:element,
+ $init: bind(template, template.init, element, model)
+ });
};
},
@@ -144,7 +141,7 @@ Compiler.prototype = {
exclusive = true;
directiveQueue = [];
}
- directiveQueue.push(bindTry(selfApi, directive, value, element, errorHandlerFor(element)));
+ directiveQueue.push(bind(selfApi, directive, value, element));
}
});
diff --git a/src/Scope.js b/src/Scope.js
index 5f1cfdda..ccf55077 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -53,6 +53,21 @@ Scope.getter = function(instance, path) {
return instance;
};
+Scope.setter = function(instance, path, value){
+ var element = path.split('.');
+ for ( var i = 0; element.length > 1; i++) {
+ var key = element.shift();
+ var newInstance = instance[key];
+ if (!newInstance) {
+ newInstance = {};
+ instance[key] = newInstance;
+ }
+ instance = newInstance;
+ }
+ instance[element.shift()] = value;
+ return value;
+};
+
Scope.prototype = {
// TODO: rename to update? or eval?
updateView: function() {
@@ -98,19 +113,8 @@ Scope.prototype = {
set: function(path, value) {
// log('SCOPE.set', path, value);
- var element = path.split('.');
var instance = this.state;
- for ( var i = 0; element.length > 1; i++) {
- var key = element.shift();
- var newInstance = instance[key];
- if (!newInstance) {
- newInstance = {};
- instance[key] = newInstance;
- }
- instance = newInstance;
- }
- instance[element.shift()] = value;
- return value;
+ return Scope.setter(instance, path, value);
},
setEval: function(expressionText, value) {
@@ -134,9 +138,9 @@ Scope.prototype = {
};
},
- eval: function(expressionText, context) {
+ eval: function(exp, context) {
// log('Scope.eval', expressionText);
- return this.compile(expressionText)(context);
+ return this.compile(exp)(context);
},
//TODO: Refactor. This function needs to be an execution closure for widgets
@@ -241,3 +245,163 @@ Scope.prototype = {
fn.apply(this.state, slice.call(arguments, 1, arguments.length));
}
};
+
+//////////////////////////////
+
+function getter(instance, path) {
+ if (!path) return instance;
+ var element = path.split('.');
+ var key;
+ var lastInstance = instance;
+ var len = element.length;
+ for ( var i = 0; i < len; i++) {
+ key = element[i];
+ if (!key.match(/^[\$\w][\$\w\d]*$/))
+ throw "Expression '" + path + "' is not a valid expression for accesing variables.";
+ if (instance) {
+ lastInstance = instance;
+ instance = instance[key];
+ }
+ if (_.isUndefined(instance) && key.charAt(0) == '$') {
+ var type = angular['Global']['typeOf'](lastInstance);
+ type = angular[type.charAt(0).toUpperCase()+type.substring(1)];
+ var fn = type ? type[[key.substring(1)]] : undefined;
+ if (fn) {
+ instance = _.bind(fn, lastInstance, lastInstance);
+ return instance;
+ }
+ }
+ }
+ if (typeof instance === 'function' && !instance['$$factory']) {
+ return bind(lastInstance, instance);
+ }
+ return instance;
+};
+
+function setter(instance, path, value){
+ var element = path.split('.');
+ for ( var i = 0; element.length > 1; i++) {
+ var key = element.shift();
+ var newInstance = instance[key];
+ if (!newInstance) {
+ newInstance = {};
+ instance[key] = newInstance;
+ }
+ instance = newInstance;
+ }
+ instance[element.shift()] = value;
+ return value;
+};
+
+var compileCache = {};
+function expressionCompile(exp){
+ if (isFunction(exp)) return exp;
+ var expFn = compileCache[exp];
+ if (!expFn) {
+ var parser = new Parser(exp);
+ expFn = parser.statements();
+ parser.assertAllConsumed();
+ compileCache[exp] = expFn;
+ }
+ // return expFn
+ // TODO(remove this hack)
+ return function(){
+ return expFn({
+ scope: {
+ set: this.$set,
+ get: this.$get
+ }
+ });
+ };
+};
+
+var NON_RENDERABLE_ELEMENTS = {
+ '#text': 1, '#comment':1, 'TR':1, 'TH':1
+};
+
+function isRenderableElement(element){
+ return element && element[0] && !NON_RENDERABLE_ELEMENTS[element[0].nodeName];
+}
+
+function rethrow(e) { throw e; }
+function errorHandlerFor(element) {
+ while (!isRenderableElement(element)) {
+ element = element.parent() || jqLite(document.body);
+ }
+ return function(error) {
+ element.attr('ng-error', angular.toJson(error));
+ element.addClass('ng-exception');
+ };
+}
+
+function scope(parent, Class) {
+ function Parent(){}
+ function API(){}
+ function Behavior(){}
+
+ var instance, behavior, api, watchList = [], evalList = [];
+
+ Class = Class || noop;
+ parent = Parent.prototype = parent || {};
+ api = API.prototype = new Parent();
+ behavior = Behavior.prototype = extend(new API(), Class.prototype);
+ instance = new Behavior();
+
+ extend(api, {
+ $parent: parent,
+ $bind: bind(instance, bind, instance),
+ $get: bind(instance, getter, instance),
+ $set: bind(instance, setter, instance),
+
+ $eval: function(exp) {
+ if (isDefined(exp)) {
+ return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length));
+ } else {
+ foreach(evalList, function(eval) {
+ instance.$tryEval(eval.fn, eval.handler);
+ });
+ foreach(watchList, function(watch) {
+ var value = instance.$tryEval(watch.watch, watch.handler);
+ if (watch.last !== value) {
+ instance.$tryEval(watch.listener, watch.handler, value, watch.last);
+ watch.last = value;
+ }
+ });
+ }
+ },
+
+ $tryEval: function (expression, exceptionHandler) {
+ try {
+ return expressionCompile(expression).apply(instance, slice.call(arguments, 2, arguments.length));
+ } catch (e) {
+ error(e);
+ if (isFunction(exceptionHandler)) {
+ exceptionHandler(e);
+ } else if (exceptionHandler) {
+ errorHandlerFor(exceptionHandler)(e);
+ }
+ }
+ },
+
+ $watch: function(watchExp, listener, exceptionHandler) {
+ var watch = expressionCompile(watchExp);
+ watchList.push({
+ watch: watch,
+ last: watch.call(instance),
+ handler: exceptionHandler,
+ listener:expressionCompile(listener)
+ });
+ },
+
+ $onEval: function(expr, exceptionHandler){
+ evalList.push({
+ fn: expressionCompile(expr),
+ handler: exceptionHandler
+ });
+ }
+ });
+
+ Class.apply(instance, slice.call(arguments, 2, arguments.length));
+
+ return instance;
+}
diff --git a/src/directives.js b/src/directives.js
index 747da3f5..10476c77 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -1,12 +1,12 @@
angularDirective("ng-init", function(expression){
- return function(){
- this.$eval(expression);
+ return function(element){
+ this.$tryEval(expression, element);
};
});
angularDirective("ng-eval", function(expression){
- return function(){
- this.$addEval(expression);
+ return function(element){
+ this.$onEval(expression, element);
};
});
@@ -14,7 +14,7 @@ angularDirective("ng-bind", function(expression){
return function(element) {
this.$watch(expression, function(value){
element.text(value);
- });
+ }, element);
};
});
@@ -45,23 +45,23 @@ angularDirective("ng-bind-template", function(expression){
var templateFn = compileBindTemplate(expression);
return function(element) {
var lastValue;
- this.$addEval(function() {
+ this.$onEval(function() {
var value = templateFn.call(this);
if (value != lastValue) {
element.text(value);
lastValue = value;
}
- });
+ }, element);
};
});
angularDirective("ng-bind-attr", function(expression){
return function(element){
- this.$addEval(function(){
+ this.$onEval(function(){
foreach(this.$eval(expression), function(value, key){
element.attr(key, compileBindTemplate(value).call(this));
}, this);
- });
+ }, element);
};
});
@@ -70,76 +70,73 @@ angularDirective("ng-non-bindable", function(){
});
angularDirective("ng-repeat", function(expression, element){
- var reference = this.comment("ng-repeat: " + expression),
- r = element.removeAttr('ng-repeat'),
- template = this.compile(element),
- 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 '" +
+ 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 '" +
+ }
+ 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];
+ }
+ valueIdent = match[3] || match[1];
+ keyIdent = match[2];
- var parent = element.parent();
- element.replaceWith(reference);
- return function(){
- var children = [],
- currentScope = this;
- this.$addEval(rhs, function(items){
+ var children = [], currentScope = this;
+ this.$onEval(function(){
var index = 0, childCount = children.length, childScope, lastElement = reference;
- foreach(items || [], function(value, key){
+ foreach(this.$tryEval(rhs, reference), function(value, key){
if (index < childCount) {
// reuse existing child
childScope = children[index];
} else {
// grow children
childScope = template(element.clone(), currentScope);
- childScope.init();
- childScope.scope.set('$index', index);
- childScope.element.attr('ng-index', index);
- lastElement.after(childScope.element);
+ lastElement.after(childScope.$element);
+ childScope.$index = index;
+ childScope.$element.attr('ng-index', index);
+ childScope.$init();
children.push(childScope);
}
- childScope.scope.set(valueIdent, value);
- if (keyIdent) childScope.scope.set(keyIdent, key);
- childScope.scope.updateView();
- lastElement = childScope.element;
+ childScope[valueIdent] = value;
+ if (keyIdent) childScope[keyIdent] = key;
+ childScope.$eval();
+ lastElement = childScope.$element;
index ++;
});
// shrink children
while(children.length > index) {
- children.pop().element.remove();
+ children.pop().$element.remove();
}
- });
+ }, reference);
};
}, {exclusive: true});
angularDirective("ng-action", function(expression, element){
- return function(){
+ return function(element){
var self = this;
element.click(function(){
- self.$eval(expression);
+ self.$tryEval(expression, element);
});
};
});
angularDirective("ng-watch", function(expression, element){
var match = expression.match(/^([^.]*):(.*)$/);
- if (!match) {
- throw "Expecting watch expression 'ident_to_watch: watch_statement' got '"
- + expression + "'";
- }
- return function(){
- this.$watch(match[1], match[2]);
+ return function(element){
+ if (!match) {
+ throw "Expecting watch expression 'ident_to_watch: watch_statement' got '"
+ + expression + "'";
+ }
+ this.$watch(match[1], match[2], element);
};
});
@@ -147,12 +144,13 @@ function ngClass(selector) {
return function(expression, element){
var existing = element[0].className + ' ';
return function(element){
- this.$addEval(expression, function(value){
+ this.$onEval(function(){
+ var value = this.$eval(expression);
if (selector(this.$index)) {
if (isArray(value)) value = value.join(' ');
element[0].className = (existing + value).replace(/\s\s+/g, ' ');
}
- });
+ }, element);
};
};
}
@@ -163,25 +161,25 @@ angularDirective("ng-class-even", ngClass(function(i){return i % 2 == 0;}));
angularDirective("ng-show", function(expression, element){
return function(element){
- this.$addEval(expression, function(value){
- element.css('display', toBoolean(value) ? '' : 'none');
- });
+ this.$onEval(function(){
+ element.css('display', toBoolean(this.$eval(expression)) ? '' : 'none');
+ }, element);
};
});
angularDirective("ng-hide", function(expression, element){
return function(element){
- this.$addEval(expression, function(value){
- element.css('display', toBoolean(value) ? 'none' : '');
- });
+ this.$onEval(function(){
+ element.css('display', toBoolean(this.$eval(expression)) ? 'none' : '');
+ }, element);
};
});
angularDirective("ng-style", function(expression, element){
return function(element){
- this.$addEval(expression, function(value){
- element.css(value);
- });
+ this.$onEval(function(){
+ element.css(this.$eval(expression));
+ }, element);
};
});
diff --git a/src/widgets2.js b/src/widgets2.js
index c4b39bc1..21da3986 100644
--- a/src/widgets2.js
+++ b/src/widgets2.js
@@ -96,7 +96,7 @@ var NG_ERROR = 'ng-error',
'radio': inputWidget('click', modelAccessor, radioAccessor, undefined),
'select-one': inputWidget('click', modelAccessor, valueAccessor, null),
'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, [])
-// 'file': [{}, 'click']
+// 'file': fileWidget???
};
function inputWidget(events, modelAccessor, viewAccessor, initValue) {