diff options
| -rw-r--r-- | src/Parser.js | 1 | ||||
| -rw-r--r-- | src/Scope.js | 6 | ||||
| -rw-r--r-- | src/jqLite.js | 13 | ||||
| -rw-r--r-- | src/widgets2.js | 176 | ||||
| -rw-r--r-- | test/ParserTest.js | 17 | ||||
| -rw-r--r-- | test/directivesSpec.js | 2 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 60 |
7 files changed, 142 insertions, 133 deletions
diff --git a/src/Parser.js b/src/Parser.js index 941d37f7..81a2afdc 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -10,6 +10,7 @@ Lexer.OPERATORS = { 'null':function(self){return null;}, 'true':function(self){return true;}, 'false':function(self){return false;}, + 'undefined':noop, '+':function(self, a,b){return (a||0)+(b||0);}, '-':function(self, a,b){return (a||0)-(b||0);}, '*':function(self, a,b){return a*b;}, diff --git a/src/Scope.js b/src/Scope.js index daafabb0..5f1cfdda 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -12,8 +12,10 @@ function Scope(initialState, name) { '$parent': initialState, '$watch': bind(self, self.addWatchListener), '$eval': bind(self, self.eval), - // change name to onEval? - '$addEval': bind(self, self.addEval) + '$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; diff --git a/src/jqLite.js b/src/jqLite.js index 2132bfc1..7646bf98 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -71,7 +71,7 @@ JQLite.prototype = { (function dealoc(element){ jqClearData(element); for ( var i = 0, children = element.childNodes; i < children.length; i++) { - dealoc(children[0]); + dealoc(children[i]); } })(this[0]); }, @@ -86,9 +86,11 @@ JQLite.prototype = { eventHandler = bind[type]; if (!eventHandler) { bind[type] = eventHandler = function() { + var value = false; foreach(eventHandler.fns, function(fn){ - fn.apply(self, arguments); + value = value || fn.apply(self, arguments); }); + return value; }; eventHandler.fns = []; addEventListener(element, type, eventHandler); @@ -98,10 +100,9 @@ JQLite.prototype = { }, trigger: function(type) { - var cache = this.data('bind'); - if (cache) { - (cache[type] || noop)(); - } + var evnt = document.createEvent('MouseEvent'); + evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + this[0].dispatchEvent(evnt); }, click: function(fn) { diff --git a/src/widgets2.js b/src/widgets2.js index 5425b5a4..b67694b1 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -1,4 +1,4 @@ -function scopeAccessor(scope, element) { +function modelAccessor(scope, element) { var expr = element.attr('name'), farmatterName = element.attr('ng-format') || NOOP, formatter = angularFormatter(farmatterName); @@ -14,7 +14,7 @@ function scopeAccessor(scope, element) { }; } -function domAccessor(element) { +function valueAccessor(element) { var validatorName = element.attr('ng-validate') || NOOP, validator = angularValidator(validatorName), required = element.attr('ng-required'), @@ -41,135 +41,67 @@ function domAccessor(element) { }; } +function checkedAccessor(element) { + var domElement = element[0]; + return { + get: function(){ return !!domElement.checked; }, + set: function(value){ domElement.checked = !!value; } + }; +} + +function radioAccessor(element) { + var domElement = element[0]; + return { + get: function(){ return domElement.checked ? domElement.value : null; }, + set: function(value){ domElement.checked = value == domElement.value; } + }; +} + +function noopAccessor() { return { get: noop, set: noop }; } + var NG_ERROR = 'ng-error', NG_VALIDATION_ERROR = 'ng-validation-error', - TEXT_META = ['', 'keyup change'], - INPUT_META = { - 'text': TEXT_META, - 'textarea': TEXT_META, - 'hidden': TEXT_META, - 'password': TEXT_META + textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''), + buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined), + INPUT_TYPE = { + 'text': textWidget, + 'textarea': textWidget, + 'hidden': textWidget, + 'password': textWidget, + 'button': buttonWidget, + 'submit': buttonWidget, + 'reset': buttonWidget, + 'image': buttonWidget, + 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false), + 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined) +// 'select-one': [null, 'change'], +// 'select-multiple': [[], 'change'], +// 'file': [{}, 'click'] }; -function inputWidget(meta) { - return meta ? function(element) { - var scope = scopeAccessor(this, element), - dom = domAccessor(element); - scope.set(dom.get() || meta[0]); - element.bind(meta[1], function(){ - scope.set(dom.get()); +function inputWidget(events, modelAccessor, viewAccessor, initValue) { + return function(element) { + var scope = this, + model = modelAccessor(scope, element), + view = viewAccessor(element), + action = element.attr('ng-action') || ''; + var value = view.get() || initValue; + if (isDefined(value)) model.set(value); + element.bind(events, function(){ + model.set(view.get()); + scope.$eval(action); }); - this.$watch(scope.get, dom.set); - } : 0; + scope.$watch(model.get, view.set); + }; } angularWidget('INPUT', function input(element){ - return inputWidget(INPUT_META[lowercase(element[0].type)]); -}); - -angularWidget('TEXTAREA', function(){ - return inputWidget(INPUT_META['text']); -}); - - - - -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// - - - -//widget related -//ng-validate, ng-required, ng-formatter -//ng-error - -//ng-scope ng-controller???? - -// <input type="text" name="bla" ng-action=""> -> <ng:textinput name="" ng-action=""/> -angular.widget("inputtext", function(element) { - var expression = element.attr('name'); - var formatter = this.formatter(element.attr('formatter')); - var validator = this.validator(element.attr('validator')); - - function validate(value) { - var error = validator(element); - if (error) { - element.addClass("ng-error"); - scope.markInvalid(this); //move out of scope - } else { - scope.clearInvalid(this); - } - } - - - element.keyup(this.withScope(function(){ - this.$evalSet(expression, formatter.parse(element.val())); - validate(element.val()); - })); - - return {watch: expression, apply: function(newValue){ - element.val(formatter.format(newValue)); - validate(element.val()); - }}; - -}); - -angular.widget("inputfile", function(element) { - -}); - -angular.widget("inputradio", function(element) { - -}); - - -// <ng:colorpicker name="chosenColor" > -angular.widget("colorpicker", function(element) { - var name = element.attr('datasource'); - var formatter = this.formatter(element.attr('ng-formatter')); - - element.colorPicker(this.withScope(function(selectedColor){ - this.$evalSet(name, formatter.parse(selectedColor)); - })); - - return function(){ - this.$watch(expression, function(cmyk){ - element.setColor(formatter.format(cmyk)); - }); + return function(element) { + this.$eval(element.attr('ng-init')||''); + (INPUT_TYPE[lowercase(element[0].type)] || noop).call(this, element); }; }); -angular.widget("template", function(element) { - var srcExpression = element.attr('src'); - var self = this; - return {watch:srcExpression, apply:function(src){ - $.load(src, function(html){ - self.destroy(element); - element.html(html); - self.compile(element); - }); - }}; +angularWidget('TEXTAREA', function(){ + return textWidget; }); - - -/** - * - * { - * withScope: //safely executes, with a try/catch. applies scope - * compile: - * widget: - * directive: - * validator: - * formatter: - * - * - * config: - * loadCSS: - * loadScript: - * loadTemplate: - * } - * - **/ diff --git a/test/ParserTest.js b/test/ParserTest.js index c8d323f2..d3813812 100644 --- a/test/ParserTest.js +++ b/test/ParserTest.js @@ -52,6 +52,16 @@ LexerTest.prototype.testTokenizeAString = function(){ assertEquals(tokens[i].string, 'd"e'); }; +LexerTest.prototype.testTokenizeUndefined = function(){ + var lexer = new Lexer("undefined"); + var tokens = lexer.parse(); + var i = 0; + assertEquals(tokens[i].index, 0); + assertEquals(tokens[i].text, 'undefined'); + assertEquals(undefined, tokens[i].fn()); +}; + + LexerTest.prototype.testTokenizeRegExp = function(){ var lexer = new Lexer("/r 1/"); @@ -486,3 +496,10 @@ ParserTest.prototype.testParsingBug = function () { var scope = new Scope(); assertEquals({a: "-"}, scope.eval("{a:'-'}")); }; + +ParserTest.prototype.testUndefined = function () { + var scope = new Scope(); + assertEquals(undefined, scope.eval("undefined")); + assertEquals(undefined, scope.eval("a=undefined")); + assertEquals(undefined, scope.get("a")); +}; diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 18bedb64..83a270c1 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -12,7 +12,7 @@ describe("directives", function(){ }; }); - afterEach(function(){ + afterEach(function() { element.remove(); expect(_(jqCache).size()).toEqual(0); }); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index aeb7a613..44a3d225 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -1,6 +1,6 @@ describe("input widget", function(){ - var compile, element, scope; + var compile, element, scope, model; beforeEach(function() { scope = null; @@ -11,6 +11,7 @@ describe("input widget", function(){ var view = compiler.compile(element)(element); view.init(); scope = view.scope; + model = scope.state; }; }); @@ -20,8 +21,9 @@ describe("input widget", function(){ }); it('should input-text auto init and handle keyup/change events', function(){ - compile('<input type="Text" name="name" value="Misko"/>'); + compile('<input type="Text" name="name" value="Misko" ng-action="count = count + 1" ng-init="count=0"/>'); expect(scope.get('name')).toEqual("Misko"); + expect(scope.get('count')).toEqual(0); scope.set('name', 'Adam'); scope.updateView(); @@ -30,10 +32,12 @@ describe("input widget", function(){ element.val('Shyam'); element.trigger('keyup'); expect(scope.get('name')).toEqual('Shyam'); + expect(scope.get('count')).toEqual(1); element.val('Kai'); element.trigger('change'); expect(scope.get('name')).toEqual('Kai'); + expect(scope.get('count')).toEqual(2); }); it("should process ng-format", function(){ @@ -98,5 +102,57 @@ describe("input widget", function(){ expect(scope.get('name')).toEqual('Kai'); }); + it('should call ng-action on button click', function(){ + compile('<input type="button" value="Click Me" ng-action="clicked = true"/>'); + element.click(); + expect(scope.get('clicked')).toEqual(true); + }); + + it('should type="checkbox"', function(){ + compile('<input type="checkbox" name="checkbox" checked ng-action="action = true"/>'); + expect(scope.get('checkbox')).toEqual(true); + element.click(); + expect(scope.get('checkbox')).toEqual(false); + expect(scope.get('action')).toEqual(true); + element.click(); + expect(scope.get('checkbox')).toEqual(true); + }); + + it('should type="radio"', function(){ + compile('<div>' + + '<input type="radio" name="chose" value="A" ng-action="clicked = 1"/>' + + '<input type="radio" name="chose" value="B" checked ng-action="clicked = 2"/>' + + '</div>'); + var a = element[0].childNodes[0]; + var b = element[0].childNodes[1]; + expect(model.chose).toEqual('B'); + expect(model.clicked).not.toBeDefined(); + model.chose = 'A'; + model.$updateView(); + expect(a.checked).toEqual(true); + + model.chose = 'B'; + model.$updateView(); + expect(a.checked).toEqual(false); + expect(b.checked).toEqual(true); + expect(model.clicked).not.toBeDefined(); + + jqLite(a).click(); + expect(model.chose).toEqual('A'); + expect(model.clicked).toEqual(1); + }); + + it('should report error on missing field', function(){ + + }); + + it('should report error on assignment error', function(){ + + }); + + it('should report error on ng-action exception', function(){ + + }); + }); |
