aboutsummaryrefslogtreecommitdiffstats
path: root/src/delete/Scope.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/delete/Scope.js')
-rw-r--r--src/delete/Scope.js407
1 files changed, 407 insertions, 0 deletions
diff --git a/src/delete/Scope.js b/src/delete/Scope.js
new file mode 100644
index 00000000..ae3f9f11
--- /dev/null
+++ b/src/delete/Scope.js
@@ -0,0 +1,407 @@
+function Scope(initialState, name) {
+ var self = this;
+ self.widgets = [];
+ self.evals = [];
+ self.watchListeners = {};
+ self.name = name;
+ initialState = initialState || {};
+ var State = function(){};
+ State.prototype = initialState;
+ self.state = new State();
+ extend(self.state, {
+ '$parent': initialState,
+ '$watch': bind(self, self.addWatchListener),
+ '$eval': bind(self, self.eval),
+ '$bind': bind(self, bind, self),
+ // change name to autoEval?
+ '$addEval': bind(self, self.addEval),
+ '$updateView': bind(self, self.updateView)
+ });
+ if (name == "ROOT") {
+ self.state['$root'] = self.state;
+ }
+};
+
+Scope.expressionCache = {};
+Scope.getter = function(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;
+};
+
+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() {
+ var self = this;
+ this.fireWatchers();
+ foreach(this.widgets, function(widget){
+ self.evalWidget(widget, "", {}, function(){
+ this.updateView(self);
+ });
+ });
+ foreach(this.evals, bind(this, this.apply));
+ },
+
+ addWidget: function(controller) {
+ if (controller) this.widgets.push(controller);
+ },
+
+ addEval: function(fn, listener) {
+ // todo: this should take a function/string and a listener
+ // todo: this is a hack, which will need to be cleaned up.
+ var self = this,
+ listenFn = listener || noop,
+ expr = self.compile(fn);
+ this.evals.push(function(){
+ self.apply(listenFn, expr());
+ });
+ },
+
+ isProperty: function(exp) {
+ for ( var i = 0; i < exp.length; i++) {
+ var ch = exp.charAt(i);
+ if (ch!='.' && !Lexer.prototype.isIdent(ch)) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ get: function(path) {
+// log('SCOPE.get', path, Scope.getter(this.state, path));
+ return Scope.getter(this.state, path);
+ },
+
+ set: function(path, value) {
+// log('SCOPE.set', path, value);
+ var instance = this.state;
+ return Scope.setter(instance, path, value);
+ },
+
+ setEval: function(expressionText, value) {
+ this.eval(expressionText + "=" + toJson(value));
+ },
+
+ compile: function(exp) {
+ if (isFunction(exp)) return bind(this.state, exp);
+ var expFn = Scope.expressionCache[exp], self = this;
+ if (!expFn) {
+ var parser = new Parser(exp);
+ expFn = parser.statements();
+ parser.assertAllConsumed();
+ Scope.expressionCache[exp] = expFn;
+ }
+ return function(context){
+ context = context || {};
+ context.self = self.state;
+ context.scope = self;
+ return expFn.call(self, context);
+ };
+ },
+
+ eval: function(exp, context) {
+// log('Scope.eval', expressionText);
+ return this.compile(exp)(context);
+ },
+
+ //TODO: Refactor. This function needs to be an execution closure for widgets
+ // move to widgets
+ // remove expression, just have inner closure.
+ evalWidget: function(widget, expression, context, onSuccess, onFailure) {
+ try {
+ var value = this.eval(expression, context);
+ if (widget.hasError) {
+ widget.hasError = false;
+ jQuery(widget.view).
+ removeClass('ng-exception').
+ removeAttr('ng-error');
+ }
+ if (onSuccess) {
+ value = onSuccess.apply(widget, [value]);
+ }
+ return true;
+ } catch (e){
+ var jsonError = toJson(e, true);
+ error('Eval Widget Error:', jsonError);
+ widget.hasError = true;
+ jQuery(widget.view).
+ addClass('ng-exception').
+ attr('ng-error', jsonError);
+ if (onFailure) {
+ onFailure.apply(widget, [e, jsonError]);
+ }
+ return false;
+ }
+ },
+
+ validate: function(expressionText, value, element) {
+ var expression = Scope.expressionCache[expressionText];
+ if (!expression) {
+ expression = new Parser(expressionText).validator();
+ Scope.expressionCache[expressionText] = expression;
+ }
+ var self = {scope:this, self:this.state, '$element':element};
+ return expression(self)(self, value);
+ },
+
+ entity: function(entityDeclaration, datastore) {
+ var expression = new Parser(entityDeclaration).entityDeclaration();
+ return expression({scope:this, datastore:datastore});
+ },
+
+ clearInvalid: function() {
+ var invalid = this.state['$invalidWidgets'];
+ while(invalid.length > 0) {invalid.pop();}
+ },
+
+ markInvalid: function(widget) {
+ this.state['$invalidWidgets'].push(widget);
+ },
+
+ watch: function(declaration) {
+ var self = this;
+ new Parser(declaration).watch()({
+ scope:this,
+ addListener:function(watch, exp){
+ self.addWatchListener(watch, function(n,o){
+ try {
+ return exp({scope:self}, n, o);
+ } catch(e) {
+ alert(e);
+ }
+ });
+ }
+ });
+ },
+
+ addWatchListener: function(watchExpression, listener) {
+ // TODO: clean me up!
+ if (!isFunction(listener)) {
+ listener = this.compile(listener);
+ }
+ var watcher = this.watchListeners[watchExpression];
+ if (!watcher) {
+ watcher = {listeners:[], expression:watchExpression};
+ this.watchListeners[watchExpression] = watcher;
+ }
+ watcher.listeners.push(listener);
+ },
+
+ fireWatchers: function() {
+ var self = this, fired = false;
+ foreach(this.watchListeners, function(watcher) {
+ var value = self.eval(watcher.expression);
+ if (value !== watcher.lastValue) {
+ foreach(watcher.listeners, function(listener){
+ listener(value, watcher.lastValue);
+ fired = true;
+ });
+ watcher.lastValue = value;
+ }
+ });
+ return fired;
+ },
+
+ apply: function(fn) {
+ 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 createScope(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;
+}