diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Angular.js | 21 | ||||
| -rw-r--r-- | src/Compiler.js | 17 | ||||
| -rw-r--r-- | src/Scope.js | 192 | ||||
| -rw-r--r-- | src/directives.js | 120 | ||||
| -rw-r--r-- | src/widgets2.js | 2 |
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) { |
