Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
feat(ngModel): provide support for custom validation handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko authored and mhevery committed Jan 24, 2014
1 parent 6778b62 commit e01d5fd
Show file tree
Hide file tree
Showing 8 changed files with 604 additions and 319 deletions.
16 changes: 10 additions & 6 deletions lib/directive/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ part 'ng_switch.dart';
part 'ng_non_bindable.dart';
part 'input_select.dart';
part 'ng_form.dart';
part 'ng_model_validators.dart';

class NgDirectiveModule extends Module {
NgDirectiveModule() {
Expand All @@ -49,14 +50,9 @@ class NgDirectiveModule extends Module {
value(NgRepeatDirective, null);
value(NgShalowRepeatDirective, null);
value(NgShowDirective, null);
value(InputEmailDirective, null);
value(InputNumberDirective, null);
value(InputTextLikeDirective, null);
value(InputRadioDirective, null);
value(InputTextDirective, null);
value(InputPasswordDirective, null);
value(InputUrlDirective, null);
value(InputCheckboxDirective, null);
value(TextAreaDirective, null);
value(InputSelectDirective, null);
value(OptionValueDirective, null);
value(ContentEditableDirective, null);
Expand All @@ -74,5 +70,13 @@ class NgDirectiveModule extends Module {
value(NgNonBindableDirective, null);
value(NgTemplateDirective, null);
value(NgForm, new NgNullForm());

value(NgModelRequiredValidator, null);
value(NgModelUrlValidator, null);
value(NgModelEmailValidator, null);
value(NgModelNumberValidator, null);
value(NgModelPatternValidator, null);
value(NgModelMinLengthValidator, null);
value(NgModelMaxLengthValidator, null);
}
}
20 changes: 18 additions & 2 deletions lib/directive/ng_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ part of angular.directive;
@NgDirective(
selector: '[ng-form]',
visibility: NgDirective.CHILDREN_VISIBILITY)
class NgForm extends NgControl implements NgDetachAware {
class NgForm extends NgControl implements NgDetachAware, Map<String, NgModel> {
final NgForm _parentForm;
final dom.Element _element;
final Scope _scope;
Expand Down Expand Up @@ -82,8 +82,24 @@ class NgForm extends NgControl implements NgDetachAware {
}
}

//FIXME: fix this reflection bug that shows up when Map is implemented
operator []=(String name, value) {
if(name == 'name'){
this.name = value;
} else {
_controlByName[name] = value;
}
}

//FIXME: fix this reflection bug that shows up when Map is implemented
operator[](name) {
return _controlByName[name];
if(name == 'valid') {
return valid;
} else if(name == 'invalid') {
return invalid;
} else {
return _controlByName[name];
}
}

addControl(NgControl control) {
Expand Down
182 changes: 34 additions & 148 deletions lib/directive/ng_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class NgModel extends NgControl {
String _exp;
String _name;

final List<_NgModelValidator> _validators = new List<_NgModelValidator>();
final Map<String, bool> currentErrors = new Map<String, bool>();

Function _removeWatch = () => null;
Expand Down Expand Up @@ -73,13 +74,24 @@ class NgModel extends NgControl {
get modelValue => getter();
set modelValue(value) => setter(value);

get validators => _validators;
validate() {
if(validators.length > 0) {
validators.forEach((validator) {
setValidity(validator.name, validator.isValid());
});
} else {
valid = true;
}
}

setValidity(String errorType, bool isValid) {
if(isValid) {
if(currentErrors.containsKey(errorType)) {
currentErrors.remove(errorType);
if(currentErrors.isEmpty) {
valid = true;
}
}
if(valid != true && currentErrors.isEmpty) {
valid = true;
}
} else if(!currentErrors.containsKey(errorType)) {
currentErrors[errorType] = true;
Expand All @@ -91,6 +103,16 @@ class NgModel extends NgControl {
}
}

addValidator(_NgModelValidator v) {
validators.add(v);
validate();
}

removeValidator(_NgModelValidator v) {
validators.remove(v);
validate();
}

destroy() {
_form.removeControl(this);
}
Expand Down Expand Up @@ -125,16 +147,18 @@ class InputCheckboxDirective {
}
}


abstract class _InputTextlikeDirective {
@NgDirective(selector: 'textarea[ng-model]')
@NgDirective(selector: 'input[ng-model]')
class InputTextLikeDirective {
dom.Element inputElement;
NgModel ngModel;
Scope scope;
String _inputType;

get typedValue => (inputElement as dynamic).value;
set typedValue(String value) => (inputElement as dynamic).value = (value == null) ? '' : value;
set typedValue(value) => (inputElement as dynamic).value = (value == null) ? '' : value.toString();

_InputTextlikeDirective(dom.Element this.inputElement, this.ngModel, this.scope) {
InputTextLikeDirective(dom.Element this.inputElement, NgModel this.ngModel, Scope this.scope) {
ngModel.render = (value) {
if (value == null) value = '';

Expand All @@ -151,152 +175,14 @@ abstract class _InputTextlikeDirective {
}

processValue() {
ngModel.validate();
var value = typedValue;
if (value != ngModel.viewValue) {
scope.$apply(() => ngModel.viewValue = value);
}
}
}

/**
* Usage:
*
* <input type="text" ng-model="name">
*
* This creates a two way databinding between the expression specified in
* ng-model and the text input element in the DOM.  If the ng-model value is
* `null`, it is treated as equivalent to the empty string for rendering
* purposes.
*/
@NgDirective(selector: 'input[type=text][ng-model]')
class InputTextDirective extends _InputTextlikeDirective {
InputTextDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
super(inputElement, ngModel, scope);

}

/**
* Usage:
*
* <input type="password" ng-model="name">
*
* This creates a two way databinding between the expression specified in
* ng-model and the password input element in the DOM.  If the ng-model value is
* `null`, it is treated as equivalent to the empty string for rendering
* purposes.
*/
@NgDirective(selector: 'input[type=password][ng-model]')
class InputPasswordDirective extends _InputTextlikeDirective {
InputPasswordDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
super(inputElement, ngModel, scope);
}

/**
* Usage:
*
* <textarea ng-model="text">
*
* This creates a two way databinding between the expression specified in
* ng-model and the textarea element in the DOM.  If the ng-model value is
* `null`, it is treated as equivalent to the empty string for rendering
* purposes.
*/
@NgDirective(selector: 'textarea[ng-model]')
class TextAreaDirective extends _InputTextlikeDirective {
TextAreaDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
super(inputElement, ngModel, scope);
}

/**
* Usage:
*
* <input type="number" ng-model="name">
*
* This creates a two way databinding between the expression specified in
* ng-model and the number input element in the DOM.  If the ng-model value is
* `null` or `NaN`, the DOM element is not updated. If the value in the DOM
* element is an invalid number, then the expression specified by the `ng-model`
* is set to null.,
*/
@NgDirective(selector: 'input[type=number][ng-model]')
class InputNumberDirective extends _InputTextlikeDirective {
InputNumberDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
super(inputElement, ngModel, scope);

get typedValue => (inputElement as dom.InputElement).valueAsNumber;

set typedValue(var value) {
if (value != null && value is num) {
num number = value as num;
if (!value.isNaN) {
(inputElement as dom.InputElement).valueAsNumber = value;
}
}
}
}

/**
* Usage:
*
* <input type="email" ng-model="emailAddress">
*
* This creates a two way databinding between the expression specified in
* ng-model and the email input element in the DOM.  If the ng-model value is
* `null`, the DOM element is not updated. If the value in the DOM element is
* an invalid e-mail address, then the expression specified by the `ng-model` is
* set to null.,
*/
@NgDirective(selector: 'input[type=email][ng-model]')
class InputEmailDirective extends _InputTextlikeDirective {
static final EMAIL_REGEXP = new RegExp(
r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$');
InputEmailDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
super(inputElement, ngModel, scope);

String get typedValue {
String value = (inputElement as dom.InputElement).value;
return EMAIL_REGEXP.hasMatch(value) ? value : null;
}

set typedValue(String value) {
if (value != null && EMAIL_REGEXP.hasMatch(value)) {
(inputElement as dom.InputElement).value = value;
}
}
}


/**
* Usage:
*
* <input type="url" ng-model="website">
*
* This creates a two way databinding between the expression specified in
* ng-model and the `url` input element in the DOM.  If the ng-model value is
* `null`, the DOM element is not updated. If the value in the DOM element is
* an invalid URL, then the expression specified by the `ng-model` is set to
* null.,
*/
@NgDirective(selector: 'input[type=url][ng-model]')
class InputUrlDirective extends _InputTextlikeDirective {
static final URL_REGEXP = new RegExp(
r'^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?' +
r'(\/|\/([\w#!:.?+=&%@!\-\/]))?$');
InputUrlDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
super(inputElement, ngModel, scope);

String get typedValue {
String value = (inputElement as dom.InputElement).value;
return URL_REGEXP.hasMatch(value) ? value : null;
}

set typedValue(String value) {
if (value != null && URL_REGEXP.hasMatch(value)) {
(inputElement as dom.InputElement).value = value;
}
}
}

class _UidCounter {
static final int CHAR_0 = "0".codeUnitAt(0);
static final int CHAR_9 = "9".codeUnitAt(0);
Expand Down Expand Up @@ -376,11 +262,11 @@ class InputRadioDirective {
* purposes.
*/
@NgDirective(selector: '[contenteditable][ng-model]')
class ContentEditableDirective extends _InputTextlikeDirective {
class ContentEditableDirective extends InputTextLikeDirective {
ContentEditableDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
super(inputElement, ngModel, scope);

// The implementation is identical to _InputTextlikeDirective but use innerHtml instead of value
// The implementation is identical to InputTextLikeDirective but use innerHtml instead of value
get typedValue => (inputElement as dynamic).innerHtml;
set typedValue(String value) => (inputElement as dynamic).innerHtml = (value == null) ? '' : value;
}
Loading

0 comments on commit e01d5fd

Please sign in to comment.