2010-01-12 01:32:33 +00:00
|
|
|
function Scope(initialState, name) {
|
2010-03-22 20:58:04 +00:00
|
|
|
var self = this;
|
|
|
|
|
self.widgets = [];
|
|
|
|
|
self.evals = [];
|
|
|
|
|
self.watchListeners = {};
|
|
|
|
|
self.name = name;
|
2010-01-06 00:36:58 +00:00
|
|
|
initialState = initialState || {};
|
|
|
|
|
var State = function(){};
|
|
|
|
|
State.prototype = initialState;
|
2010-03-22 20:58:04 +00:00
|
|
|
self.state = new State();
|
|
|
|
|
extend(self.state, {
|
|
|
|
|
'$parent': initialState,
|
|
|
|
|
'$watch': bind(self, self.addWatchListener),
|
|
|
|
|
'$eval': bind(self, self.eval),
|
2010-03-25 20:01:08 +00:00
|
|
|
'$bind': bind(self, bind, self),
|
|
|
|
|
// change name to autoEval?
|
|
|
|
|
'$addEval': bind(self, self.addEval),
|
|
|
|
|
'$updateView': bind(self, self.updateView)
|
2010-03-22 20:58:04 +00:00
|
|
|
});
|
2010-01-06 00:36:58 +00:00
|
|
|
if (name == "ROOT") {
|
2010-03-22 20:58:04 +00:00
|
|
|
self.state['$root'] = self.state;
|
2010-01-06 00:36:58 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2010-01-09 23:02:43 +00:00
|
|
|
Scope.expressionCache = {};
|
|
|
|
|
Scope.getter = function(instance, path) {
|
2010-01-06 00:36:58 +00:00
|
|
|
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) == '$') {
|
2010-01-10 16:58:57 +00:00
|
|
|
var type = angular['Global']['typeOf'](lastInstance);
|
2010-01-06 00:36:58 +00:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2010-01-25 01:10:58 +00:00
|
|
|
if (typeof instance === 'function' && !instance['$$factory']) {
|
2010-01-09 23:02:43 +00:00
|
|
|
return bind(lastInstance, instance);
|
2010-01-06 00:36:58 +00:00
|
|
|
}
|
|
|
|
|
return instance;
|
|
|
|
|
};
|
|
|
|
|
|
2010-03-26 05:03:11 +00:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
Scope.prototype = {
|
2010-03-22 20:58:04 +00:00
|
|
|
// TODO: rename to update? or eval?
|
2010-01-12 01:32:33 +00:00
|
|
|
updateView: function() {
|
|
|
|
|
var self = this;
|
|
|
|
|
this.fireWatchers();
|
2010-03-18 21:43:49 +00:00
|
|
|
foreach(this.widgets, function(widget){
|
2010-01-12 01:32:33 +00:00
|
|
|
self.evalWidget(widget, "", {}, function(){
|
|
|
|
|
this.updateView(self);
|
|
|
|
|
});
|
|
|
|
|
});
|
2010-03-18 21:43:49 +00:00
|
|
|
foreach(this.evals, bind(this, this.apply));
|
2010-01-12 01:32:33 +00:00
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
addWidget: function(controller) {
|
|
|
|
|
if (controller) this.widgets.push(controller);
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-03-18 21:43:49 +00:00
|
|
|
addEval: function(fn, listener) {
|
|
|
|
|
// todo: this should take a function/string and a listener
|
2010-03-22 20:58:04 +00:00
|
|
|
// todo: this is a hack, which will need to be cleaned up.
|
|
|
|
|
var self = this,
|
|
|
|
|
listenFn = listener || noop,
|
2010-03-23 03:20:05 +00:00
|
|
|
expr = self.compile(fn);
|
2010-03-22 20:58:04 +00:00
|
|
|
this.evals.push(function(){
|
|
|
|
|
self.apply(listenFn, expr());
|
|
|
|
|
});
|
2010-03-18 21:43:49 +00:00
|
|
|
},
|
|
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
isProperty: function(exp) {
|
|
|
|
|
for ( var i = 0; i < exp.length; i++) {
|
|
|
|
|
var ch = exp.charAt(i);
|
|
|
|
|
if (ch!='.' && !Lexer.prototype.isIdent(ch)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2010-01-06 00:36:58 +00:00
|
|
|
}
|
2010-01-12 01:32:33 +00:00
|
|
|
return true;
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
get: function(path) {
|
2010-01-25 01:10:58 +00:00
|
|
|
// log('SCOPE.get', path, Scope.getter(this.state, path));
|
2010-01-12 01:32:33 +00:00
|
|
|
return Scope.getter(this.state, path);
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
set: function(path, value) {
|
2010-01-25 01:10:58 +00:00
|
|
|
// log('SCOPE.set', path, value);
|
2010-01-12 01:32:33 +00:00
|
|
|
var instance = this.state;
|
2010-03-26 05:03:11 +00:00
|
|
|
return Scope.setter(instance, path, value);
|
2010-01-12 01:32:33 +00:00
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
setEval: function(expressionText, value) {
|
|
|
|
|
this.eval(expressionText + "=" + toJson(value));
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-03-22 20:58:04 +00:00
|
|
|
compile: function(exp) {
|
2010-03-23 21:57:11 +00:00
|
|
|
if (isFunction(exp)) return bind(this.state, exp);
|
2010-03-23 03:20:05 +00:00
|
|
|
var expFn = Scope.expressionCache[exp], self = this;
|
2010-03-22 20:58:04 +00:00
|
|
|
if (!expFn) {
|
|
|
|
|
var parser = new Parser(exp);
|
|
|
|
|
expFn = parser.statements();
|
2010-01-12 01:32:33 +00:00
|
|
|
parser.assertAllConsumed();
|
2010-03-22 20:58:04 +00:00
|
|
|
Scope.expressionCache[exp] = expFn;
|
2010-01-06 00:36:58 +00:00
|
|
|
}
|
2010-03-23 03:20:05 +00:00
|
|
|
return function(context){
|
|
|
|
|
context = context || {};
|
|
|
|
|
context.self = self.state;
|
|
|
|
|
context.scope = self;
|
|
|
|
|
return expFn.call(self, context);
|
|
|
|
|
};
|
2010-03-22 20:58:04 +00:00
|
|
|
},
|
|
|
|
|
|
2010-03-26 05:03:11 +00:00
|
|
|
eval: function(exp, context) {
|
2010-03-22 20:58:04 +00:00
|
|
|
// log('Scope.eval', expressionText);
|
2010-03-26 05:03:11 +00:00
|
|
|
return this.compile(exp)(context);
|
2010-01-12 01:32:33 +00:00
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
//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);
|
2010-03-15 21:36:50 +00:00
|
|
|
error('Eval Widget Error:', jsonError);
|
2010-01-12 01:32:33 +00:00
|
|
|
widget.hasError = true;
|
|
|
|
|
jQuery(widget.view).
|
|
|
|
|
addClass('ng-exception').
|
|
|
|
|
attr('ng-error', jsonError);
|
|
|
|
|
if (onFailure) {
|
|
|
|
|
onFailure.apply(widget, [e, jsonError]);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2010-01-06 00:36:58 +00:00
|
|
|
}
|
2010-01-12 01:32:33 +00:00
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-02-12 22:16:33 +00:00
|
|
|
validate: function(expressionText, value, element) {
|
2010-01-12 01:32:33 +00:00
|
|
|
var expression = Scope.expressionCache[expressionText];
|
|
|
|
|
if (!expression) {
|
|
|
|
|
expression = new Parser(expressionText).validator();
|
|
|
|
|
Scope.expressionCache[expressionText] = expression;
|
2010-01-06 00:36:58 +00:00
|
|
|
}
|
2010-02-12 22:16:33 +00:00
|
|
|
var self = {scope:this, self:this.state, '$element':element};
|
2010-01-12 01:32:33 +00:00
|
|
|
return expression(self)(self, value);
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-25 01:10:58 +00:00
|
|
|
entity: function(entityDeclaration, datastore) {
|
2010-01-12 01:32:33 +00:00
|
|
|
var expression = new Parser(entityDeclaration).entityDeclaration();
|
2010-01-25 01:10:58 +00:00
|
|
|
return expression({scope:this, datastore:datastore});
|
2010-01-12 01:32:33 +00:00
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-02-04 22:02:20 +00:00
|
|
|
clearInvalid: function() {
|
|
|
|
|
var invalid = this.state['$invalidWidgets'];
|
|
|
|
|
while(invalid.length > 0) {invalid.pop();}
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
markInvalid: function(widget) {
|
2010-01-25 03:33:04 +00:00
|
|
|
this.state['$invalidWidgets'].push(widget);
|
2010-01-12 01:32:33 +00:00
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
addWatchListener: function(watchExpression, listener) {
|
2010-03-22 23:07:42 +00:00
|
|
|
// TODO: clean me up!
|
|
|
|
|
if (!isFunction(listener)) {
|
2010-03-23 03:20:05 +00:00
|
|
|
listener = this.compile(listener);
|
2010-03-22 23:07:42 +00:00
|
|
|
}
|
2010-01-12 01:32:33 +00:00
|
|
|
var watcher = this.watchListeners[watchExpression];
|
|
|
|
|
if (!watcher) {
|
|
|
|
|
watcher = {listeners:[], expression:watchExpression};
|
|
|
|
|
this.watchListeners[watchExpression] = watcher;
|
2010-01-06 00:36:58 +00:00
|
|
|
}
|
2010-01-12 01:32:33 +00:00
|
|
|
watcher.listeners.push(listener);
|
|
|
|
|
},
|
2010-03-15 21:36:50 +00:00
|
|
|
|
2010-01-12 01:32:33 +00:00
|
|
|
fireWatchers: function() {
|
2010-03-18 21:43:49 +00:00
|
|
|
var self = this, fired = false;
|
2010-01-12 01:32:33 +00:00
|
|
|
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;
|
2010-03-18 19:20:06 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
apply: function(fn) {
|
2010-03-18 21:43:49 +00:00
|
|
|
fn.apply(this.state, slice.call(arguments, 1, arguments.length));
|
2010-01-12 01:32:33 +00:00
|
|
|
}
|
2010-03-15 21:36:50 +00:00
|
|
|
};
|
2010-03-26 05:03:11 +00:00
|
|
|
|
|
|
|
|
//////////////////////////////
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|