From 9ee2cdff44e7d496774b340de816344126c457b3 Mon Sep 17 00:00:00 2001
From: Misko Hevery
Date: Tue, 22 Nov 2011 21:28:39 -0800
Subject: refactor(directives): connect new compiler
- turn everything into a directive
---
src/widget/form.js | 51 ++---
src/widget/input.js | 89 +++------
src/widget/select.js | 554 ++++++++++++++++++++++++++-------------------------
3 files changed, 342 insertions(+), 352 deletions(-)
(limited to 'src/widget')
diff --git a/src/widget/form.js b/src/widget/form.js
index 962cb6b8..405aae74 100644
--- a/src/widget/form.js
+++ b/src/widget/form.js
@@ -82,28 +82,31 @@
*/
-angularWidget('form', function(form){
- this.descend(true);
- this.directives(true);
- return ['$formFactory', '$element', function($formFactory, formElement) {
- var name = formElement.attr('name'),
- parentForm = $formFactory.forElement(formElement),
- form = $formFactory(parentForm);
- formElement.data('$form', form);
- formElement.bind('submit', function(event) {
- if (!formElement.attr('action')) event.preventDefault();
- });
- if (name) {
- this[name] = form;
+var ngFormDirective = ['$formFactory', function($formFactory) {
+ return {
+ restrict: 'E',
+ compile: function() {
+ return {
+ pre: function(scope, formElement, attr) {
+ var name = attr.name,
+ parentForm = $formFactory.forElement(formElement),
+ form = $formFactory(parentForm);
+ formElement.data('$form', form);
+ formElement.bind('submit', function(event){
+ if (!attr.action) event.preventDefault();
+ });
+ if (name) {
+ scope[name] = form;
+ }
+ watch('valid');
+ watch('invalid');
+ function watch(name) {
+ form.$watch('$' + name, function(value) {
+ formElement[value ? 'addClass' : 'removeClass']('ng-' + name);
+ });
+ }
+ }
+ };
}
- watch('valid');
- watch('invalid');
- function watch(name) {
- form.$watch('$' + name, function(value) {
- formElement[value ? 'addClass' : 'removeClass']('ng-' + name);
- });
- }
- }];
-});
-
-angularWidget('ng:form', angularWidget('form'));
+ };
+}];
diff --git a/src/widget/input.js b/src/widget/input.js
index e666a0c1..9f9d9852 100644
--- a/src/widget/input.js
+++ b/src/widget/input.js
@@ -542,23 +542,23 @@ angularInputType('checkbox', function(inputElement, widget) {
*/
-angularInputType('radio', function(inputElement, widget) {
+angularInputType('radio', function(inputElement, widget, attr) {
//correct the name
- inputElement.attr('name', widget.$id + '@' + inputElement.attr('name'));
+ attr.$set('name', widget.$id + '@' + attr.name);
inputElement.bind('click', function() {
widget.$apply(function() {
if (inputElement[0].checked) {
- widget.$emit('$viewChange', widget.$value);
+ widget.$emit('$viewChange', attr.value);
}
});
});
widget.$render = function() {
- inputElement[0].checked = isDefined(widget.$value) && (widget.$value == widget.$viewValue);
+ inputElement[0].checked = isDefined(attr.value) && (attr.value == widget.$viewValue);
};
if (inputElement[0].checked) {
- widget.$viewValue = widget.$value;
+ widget.$viewValue = attr.value;
}
});
@@ -664,28 +664,28 @@ var HTML5_INPUTS_TYPES = makeMap(
it('should initialize to model', function() {
- expect(binding('user')).toEqual('{\n \"last\":\"visitor",\n \"name\":\"guest\"}');
+ expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}');
expect(binding('myForm.userName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});
it('should be invalid if empty when required', function() {
input('user.name').enter('');
- expect(binding('user')).toEqual('{\n \"last\":\"visitor",\n \"name\":\"\"}');
+ expect(binding('user')).toEqual('{"last":"visitor","name":""}');
expect(binding('myForm.userName.$valid')).toEqual('false');
expect(binding('myForm.$valid')).toEqual('false');
});
it('should be valid if empty when min length is set', function() {
input('user.last').enter('');
- expect(binding('user')).toEqual('{\n \"last\":\"",\n \"name\":\"guest\"}');
+ expect(binding('user')).toEqual('{"last":"","name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('true');
expect(binding('myForm.$valid')).toEqual('true');
});
it('should be invalid if less than required min length', function() {
input('user.last').enter('xx');
- expect(binding('user')).toEqual('{\n \"last\":\"xx",\n \"name\":\"guest\"}');
+ expect(binding('user')).toEqual('{"last":"xx","name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/MINLENGTH/);
expect(binding('myForm.$valid')).toEqual('false');
@@ -694,7 +694,7 @@ var HTML5_INPUTS_TYPES = makeMap(
it('should be valid if longer than max length', function() {
input('user.last').enter('some ridiculously long name');
expect(binding('user'))
- .toEqual('{\n \"last\":\"some ridiculously long name",\n \"name\":\"guest\"}');
+ .toEqual('{"last":"some ridiculously long name","name":"guest"}');
expect(binding('myForm.lastName.$valid')).toEqual('false');
expect(binding('myForm.lastName.$error')).toMatch(/MAXLENGTH/);
expect(binding('myForm.$valid')).toEqual('false');
@@ -702,26 +702,24 @@ var HTML5_INPUTS_TYPES = makeMap(
*/
-angularWidget('input', function(inputElement){
- this.directives(true);
- this.descend(true);
- var modelExp = inputElement.attr('ng:model');
- return modelExp &&
- ['$defer', '$formFactory', '$element',
- function($defer, $formFactory, inputElement) {
+var inputDirective = ['$defer', '$formFactory', function($defer, $formFactory) {
+ return {
+ restrict: 'E',
+ link: function(modelScope, inputElement, attr) {
+ if (!attr.ngModel) return;
+
var form = $formFactory.forElement(inputElement),
// We have to use .getAttribute, since jQuery tries to be smart and use the
// type property. Trouble is some browser change unknown to text.
- type = inputElement[0].getAttribute('type') || 'text',
+ type = attr.type || 'text',
TypeController,
- modelScope = this,
patternMatch, widget,
- pattern = trim(inputElement.attr('ng:pattern')),
- minlength = parseInt(inputElement.attr('ng:minlength'), 10),
- maxlength = parseInt(inputElement.attr('ng:maxlength'), 10),
+ pattern = attr.ngPattern,
+ modelExp = attr.ngModel,
+ minlength = parseInt(attr.ngMinlength, 10),
+ maxlength = parseInt(attr.ngMaxlength, 10),
loadFromScope = type.match(/^\s*\@\s*(.*)/);
-
if (!pattern) {
patternMatch = valueFn(true);
} else {
@@ -743,7 +741,7 @@ angularWidget('input', function(inputElement){
type = lowercase(type);
TypeController = (loadFromScope
- ? (assertArgFn(this.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn
+ ? (assertArgFn(modelScope.$eval(loadFromScope[1]), loadFromScope[1])).$unboundFn
: angularInputType(type)) || noop;
if (!HTML5_INPUTS_TYPES[type]) {
@@ -757,26 +755,21 @@ angularWidget('input', function(inputElement){
}
//TODO(misko): setting $inject is a hack
- !TypeController.$inject && (TypeController.$inject = ['$element', '$scope']);
+ !TypeController.$inject && (TypeController.$inject = ['$element', '$scope', '$attr']);
widget = form.$createWidget({
scope: modelScope,
model: modelExp,
- onChange: inputElement.attr('ng:change'),
- alias: inputElement.attr('name'),
+ onChange: attr.ngChange,
+ alias: attr.name,
controller: TypeController,
- controllerArgs: {$element: inputElement}
+ controllerArgs: {$element: inputElement, $attr: attr}
});
- watchElementProperty(this, widget, 'value', inputElement);
- watchElementProperty(this, widget, 'required', inputElement);
- watchElementProperty(this, widget, 'readonly', inputElement);
- watchElementProperty(this, widget, 'disabled', inputElement);
-
widget.$pristine = !(widget.$dirty = false);
widget.$on('$validate', function() {
var $viewValue = trim(widget.$viewValue),
- inValid = widget.$required && !$viewValue,
+ inValid = attr.required && !$viewValue,
tooLong = maxlength && $viewValue && $viewValue.length > maxlength,
tooShort = minlength && $viewValue && $viewValue.length < minlength,
missMatch = $viewValue && !patternMatch($viewValue);
@@ -812,7 +805,7 @@ angularWidget('input', function(inputElement){
inputElement.val(widget.$viewValue || '');
};
- inputElement.bind('keydown change input', function(event) {
+ inputElement.bind('keydown change input', function(event){
var key = event.keyCode;
if (/*command*/ key != 91 &&
/*modifiers*/ !(15 < key && key < 19) &&
@@ -827,8 +820,9 @@ angularWidget('input', function(inputElement){
}
});
}
- }];
-});
+ }
+ };
+}];
/**
@@ -856,24 +850,3 @@ angularWidget('input', function(inputElement){
* @param {string=} ng:change Angular expression to be executed when input changes due to user
* interaction with the input element.
*/
-angularWidget('textarea', angularWidget('input'));
-
-
-function watchElementProperty(modelScope, widget, name, element) {
- var bindAttr = fromJson(element.attr('ng:bind-attr') || '{}'),
- match = /\s*\{\{(.*)\}\}\s*/.exec(bindAttr[name]),
- isBoolean = BOOLEAN_ATTR[name];
- widget['$' + name] = isBoolean
- ? ( // some browsers return true some '' when required is set without value.
- isString(element.prop(name)) || !!element.prop(name) ||
- // this is needed for ie9, since it will treat boolean attributes as false
- !!element[0].attributes[name])
- : element.attr(name);
- if (bindAttr[name] && match) {
- modelScope.$watch(match[1], function(value) {
- widget['$' + name] = isBoolean ? !!value : value;
- widget.$emit('$validate');
- widget.$render && widget.$render();
- });
- }
-}
diff --git a/src/widget/select.js b/src/widget/select.js
index a3633ddd..ae9e1b7c 100644
--- a/src/widget/select.js
+++ b/src/widget/select.js
@@ -112,321 +112,335 @@
it('should check ng:options', function() {
- expect(binding('color')).toMatch('red');
+ expect(binding('{selected_color:color}')).toMatch('red');
select('color').option('0');
- expect(binding('color')).toMatch('black');
+ expect(binding('{selected_color:color}')).toMatch('black');
using('.nullable').select('color').option('');
- expect(binding('color')).toMatch('null');
+ expect(binding('{selected_color:color}')).toMatch('null');
});
*/
-
- //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
-var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/;
-
-
-angularWidget('select', function(element){
- this.directives(true);
- this.descend(true);
- return element.attr('ng:model') &&
- ['$formFactory', '$compile', '$parse', '$element',
- function($formFactory, $compile, $parse, selectElement){
- var modelScope = this,
- match,
- form = $formFactory.forElement(selectElement),
- multiple = selectElement.attr('multiple'),
- optionsExp = selectElement.attr('ng:options'),
- modelExp = selectElement.attr('ng:model'),
- widget = form.$createWidget({
- scope: modelScope,
- model: modelExp,
- onChange: selectElement.attr('ng:change'),
- alias: selectElement.attr('name'),
- controller: ['$scope', optionsExp ? Options : (multiple ? Multiple : Single)]});
-
- selectElement.bind('$destroy', function() { widget.$destroy(); });
-
- widget.$pristine = !(widget.$dirty = false);
-
- watchElementProperty(modelScope, widget, 'required', selectElement);
- watchElementProperty(modelScope, widget, 'readonly', selectElement);
- watchElementProperty(modelScope, widget, 'disabled', selectElement);
-
- widget.$on('$validate', function() {
- var valid = !widget.$required || !!widget.$modelValue;
- if (valid && multiple && widget.$required) valid = !!widget.$modelValue.length;
- if (valid !== !widget.$error.REQUIRED) {
- widget.$emit(valid ? '$valid' : '$invalid', 'REQUIRED');
- }
- });
-
- widget.$on('$viewChange', function() {
- widget.$pristine = !(widget.$dirty = true);
- });
-
- forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) {
- widget.$watch('$' + name, function(value) {
- selectElement[value ? 'addClass' : 'removeClass']('ng-' + name);
+var ngOptionsDirective = valueFn({ terminal: true });
+var selectDirective = ['$formFactory', '$compile', '$parse',
+ function($formFactory, $compile, $parse){
+ //00001111100000000000222200000000000000000000003333000000000000044444444444444444000000000555555555555555550000000666666666666666660000000000000007777
+ var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/;
+
+ return {
+ restrict: 'E',
+ link: function(modelScope, selectElement, attr) {
+ if (!attr.ngModel) return;
+ var form = $formFactory.forElement(selectElement),
+ multiple = attr.multiple,
+ optionsExp = attr.ngOptions,
+ modelExp = attr.ngModel,
+ widget = form.$createWidget({
+ scope: modelScope,
+ model: modelExp,
+ onChange: attr.ngChange,
+ alias: attr.name,
+ controller: ['$scope', optionsExp ? Options : (multiple ? Multiple : Single)]});
+
+ selectElement.bind('$destroy', function() { widget.$destroy(); });
+
+ widget.$pristine = !(widget.$dirty = false);
+
+ widget.$on('$validate', function() {
+ var valid = !attr.required || !!widget.$modelValue;
+ if (valid && multiple && attr.required) valid = !!widget.$modelValue.length;
+ if (valid !== !widget.$error.REQUIRED) {
+ widget.$emit(valid ? '$valid' : '$invalid', 'REQUIRED');
+ }
});
- });
- ////////////////////////////
+ widget.$on('$viewChange', function() {
+ widget.$pristine = !(widget.$dirty = true);
+ });
- function Multiple(widget) {
- widget.$render = function() {
- var items = new HashMap(widget.$viewValue);
- forEach(selectElement.children(), function(option){
- option.selected = isDefined(items.get(option.value));
+ forEach(['valid', 'invalid', 'pristine', 'dirty'], function(name) {
+ widget.$watch('$' + name, function(value) {
+ selectElement[value ? 'addClass' : 'removeClass']('ng-' + name);
});
- };
+ });
+
+ ////////////////////////////
- selectElement.bind('change', function() {
- widget.$apply(function() {
- var array = [];
+ function Multiple(widget) {
+ widget.$render = function() {
+ var items = new HashMap(this.$viewValue);
forEach(selectElement.children(), function(option){
- if (option.selected) {
- array.push(option.value);
- }
+ option.selected = isDefined(items.get(option.value));
+ });
+ };
+
+ selectElement.bind('change', function() {
+ widget.$apply(function() {
+ var array = [];
+ forEach(selectElement.children(), function(option){
+ if (option.selected) {
+ array.push(option.value);
+ }
+ });
+ widget.$emit('$viewChange', array);
});
- widget.$emit('$viewChange', array);
});
- });
- }
+ }
- function Single(widget) {
- widget.$render = function() {
- selectElement.val(widget.$viewValue);
- };
+ function Single(widget) {
+ widget.$render = function() {
+ selectElement.val(widget.$viewValue);
+ };
- selectElement.bind('change', function() {
- widget.$apply(function() {
- widget.$emit('$viewChange', selectElement.val());
+ selectElement.bind('change', function() {
+ widget.$apply(function() {
+ widget.$emit('$viewChange', selectElement.val());
+ });
});
- });
-
- widget.$viewValue = selectElement.val();
- }
-
- function Options(widget) {
- var match;
- if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) {
- throw Error(
- "Expected ng:options in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
- " but got '" + optionsExp + "'.");
+ widget.$viewValue = selectElement.val();
}
- var displayFn = $parse(match[2] || match[1]),
- valueName = match[4] || match[6],
- keyName = match[5],
- groupByFn = $parse(match[3] || ''),
- valueFn = $parse(match[2] ? match[1] : valueName),
- valuesFn = $parse(match[7]),
- // we can't just jqLite('