diff options
Diffstat (limited to 'src/delete/Scope.js')
| -rw-r--r-- | src/delete/Scope.js | 407 |
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; +} |
