aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/Parser.js1
-rw-r--r--src/Scope.js6
-rw-r--r--src/jqLite.js13
-rw-r--r--src/widgets2.js176
-rw-r--r--test/ParserTest.js17
-rw-r--r--test/directivesSpec.js2
-rw-r--r--test/widgetsSpec.js60
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(){
+
+ });
+
});