From aabaa985d62951beeecd7e9a28e18e48faadc707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 11 Feb 2014 14:29:30 -0500 Subject: [PATCH 001/102] refactor(forms): place all shared logic of forms and models into NgControl --- lib/directive/module.dart | 1 + lib/directive/ng_control.dart | 126 +++++++++++++++++++++- lib/directive/ng_form.dart | 140 +++---------------------- lib/directive/ng_model.dart | 61 ++--------- lib/directive/ng_model_validators.dart | 14 +-- test/directive/ng_form_spec.dart | 18 ++-- test/directive/ng_model_spec.dart | 55 +++++----- 7 files changed, 193 insertions(+), 222 deletions(-) diff --git a/lib/directive/module.dart b/lib/directive/module.dart index 0e6004392..e92ab8ceb 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -70,6 +70,7 @@ class NgDirectiveModule extends Module { value(NgStyleDirective, null); value(NgNonBindableDirective, null); value(NgTemplateDirective, null); + value(NgControl, new NgNullControl()); value(NgForm, new NgNullForm()); value(NgModelRequiredValidator, null); diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart index 56cc53e63..a9042c75d 100644 --- a/lib/directive/ng_control.dart +++ b/lib/directive/ng_control.dart @@ -1,6 +1,6 @@ part of angular.directive; -abstract class NgControl { +abstract class NgControl implements NgDetachAware { static const NG_VALID_CLASS = "ng-valid"; static const NG_INVALID_CLASS = "ng-invalid"; static const NG_PRISTINE_CLASS = "ng-pristine"; @@ -12,10 +12,33 @@ abstract class NgControl { bool _valid; bool _invalid; - get element => null; + final Scope _scope; + final NgControl _parentControl; + dom.Element _element; + + final Map> errors = new Map>(); + final List _controls = new List(); + final Map _controlByName = new Map(); + + NgControl(Scope this._scope, dom.Element this._element, Injector injector) + : _parentControl = injector.parent.get(NgControl) + { + pristine = true; + } + + detach() { + for (int i = _controls.length - 1; i >= 0; --i) { + removeControl(_controls[i]); + } + } get name => _name; - set name(name) => _name = name; + set name(value) { + _name = value; + _parentControl.addControl(this); + } + + get element => _element; get pristine => _pristine; set pristine(value) { @@ -31,6 +54,10 @@ abstract class NgControl { _pristine = false; element.classes..remove(NG_PRISTINE_CLASS)..add(NG_DIRTY_CLASS); + + //as soon as one of the controls/models is modified + //then all of the parent controls are dirty as well + _parentControl.dirty = true; } get valid => _valid; @@ -49,4 +76,97 @@ abstract class NgControl { element.classes..remove(NG_VALID_CLASS)..add(NG_INVALID_CLASS); } + /** + * Registers a form control into the form for validation. + * + * * [control] - The form control which will be registered (see [ngControl]). + */ + addControl(NgControl control) { + _controls.add(control); + if (control.name != null) { + _controlByName[control.name] = control; + } + } + + /** + * De-registers a form control from the list of controls associated with the + * form. + * + * * [control] - The form control which will be de-registered (see + * [ngControl]). + */ + removeControl(NgControl control) { + _controls.remove(control); + if (control.name != null) { + _controlByName.remove(control.name); + } + } + + /** + * Sets the validity status of the given control/errorType pair within + * the list of controls registered on the form. Depending on the validation + * state of the existing controls, this will either change valid to true + * or invalid to true depending on if all controls are valid or if one + * or more of them is invalid. + * + * * [control] - The registered control object (see [ngControl]). + * * [errorType] - The error associated with the control (e.g. required, url, + * number, etc...). + * * [isValid] - Whether the given error is valid or not (false would mean the + * error is real). + */ + updateControlValidity(NgControl control, String errorType, bool isValid) { + List queue = errors[errorType]; + + if (isValid) { + if (queue != null) { + queue.remove(control); + if (queue.isEmpty) { + errors.remove(errorType); + _parentControl.updateControlValidity(this, errorType, true); + } + } + if (errors.isEmpty) { + valid = true; + } + } else { + if (queue == null) { + queue = new List(); + errors[errorType] = queue; + _parentControl.updateControlValidity(this, errorType, false); + } else if (queue.contains(control)) return; + + queue.add(control); + invalid = true; + } + } +} + +class NgNullControl implements NgControl { + var _name, _dirty, _valid, _invalid, _pristine, _element; + var _controls, _scope, _parentControl, _controlName; + var errors, _controlByName; + dom.Element element; + NgNullControl() {} + + addControl(control) {} + removeControl(control) {} + updateControlValidity(NgControl control, String errorType, bool isValid) {} + + get name => null; + set name(name) {} + + get pristine => null; + set pristine(value) {} + + get dirty => null; + set dirty(value) {} + + get valid => null; + set valid(value) {} + + get invalid => null; + set invalid(value) {} + + detach() => null; } diff --git a/lib/directive/ng_form.dart b/lib/directive/ng_form.dart index a9651e4f4..ce42e5217 100644 --- a/lib/directive/ng_form.dart +++ b/lib/directive/ng_form.dart @@ -7,27 +7,21 @@ part of angular.directive; */ @NgDirective( selector: 'form', + publishTypes : const [NgControl], visibility: NgDirective.CHILDREN_VISIBILITY) @NgDirective( selector: 'fieldset', + publishTypes : const [NgControl], visibility: NgDirective.CHILDREN_VISIBILITY) @NgDirective( selector: '.ng-form', + publishTypes : const [NgControl], visibility: NgDirective.CHILDREN_VISIBILITY) @NgDirective( selector: '[ng-form]', + publishTypes : const [NgControl], visibility: NgDirective.CHILDREN_VISIBILITY) -class NgForm extends NgControl implements NgDetachAware, Map { - final NgForm _parentForm; - final dom.Element _element; - final Scope _scope; - - final Map> errors = - new Map>(); - - final List _controls = new List(); - final Map _controlByName = new Map(); - +class NgForm extends NgControl implements Map { /** * Instantiates a new instance of NgForm. Upon creation, the instance of the * class will be bound to the formName property on the scope (where formName @@ -38,73 +32,26 @@ class NgForm extends NgControl implements NgDetachAware, Map { * * [element] - The form DOM element. * * [injector] - An instance of Injector. */ - NgForm(this._scope, dom.Element this._element, Injector injector): - _parentForm = injector.parent.get(NgForm) - { - if(!_element.attributes.containsKey('action')) { - _element.onSubmit.listen((event) { + NgForm(Scope scope, dom.Element element, Injector injector) : + super(scope, element, injector) { + + if (!element.attributes.containsKey('action')) { + element.onSubmit.listen((event) { event.preventDefault(); }); } - - pristine = true; - } - - detach() { - for (int i = _controls.length - 1; i >= 0; --i) { - removeControl(_controls[i]); - } } - get element => _element; - @NgAttr('name') get name => _name; - set name(name) { - _name = name; + set name(value) { + super.name = value; _scope[name] = this; } - /** - * Sets the validity status of the given control/errorType pair within - * the list of controls registered on the form. Depending on the validation - * state of the existing controls, this will either change valid to true - * or invalid to true depending on if all controls are valid or if one - * or more of them is invalid. - * - * * [control] - The registered control object (see [ngControl]). - * * [errorType] - The error associated with the control (e.g. required, url, - * number, etc...). - * * [isValid] - Whether the given error is valid or not (false would mean the - * error is real). - */ - setValidity(NgControl control, String errorType, bool isValid) { - List queue = errors[errorType]; - - if(isValid) { - if(queue != null) { - queue.remove(control); - if(queue.isEmpty) { - errors.remove(errorType); - if(errors.isEmpty) valid = true; - _parentForm.setValidity(this, errorType, true); - } - } - } else { - if(queue == null) { - queue = new List(); - errors[errorType] = queue; - _parentForm.setValidity(this, errorType, false); - } else if(queue.contains(control)) return; - - queue.add(control); - invalid = true; - } - } - //FIXME: fix this reflection bug that shows up when Map is implemented operator []=(String key, value) { - if(key == 'name'){ + if (key == 'name') { name = value; } else { _controlByName[key] = value; @@ -113,45 +60,15 @@ class NgForm extends NgControl implements NgDetachAware, Map { //FIXME: fix this reflection bug that shows up when Map is implemented operator[](name) { - if(name == 'valid') { + if (name == 'valid') { return valid; - } else if(name == 'invalid') { + } else if (name == 'invalid') { return invalid; } else { return _controlByName[name]; } } - /** - * Registers a form control into the form for validation. - * - * * [control] - The form control which will be registered (see [ngControl]). - */ - addControl(NgControl control) { - _controls.add(control); - if(control.name != null) { - _controlByName[control.name] = control; - } - } - - /** - * De-registers a form control from the list of controls associated with the - * form. - * - * * [control] - The form control which will be de-registered (see - * [ngControl]). - */ - removeControl(NgControl control) { - _controls.remove(control); - if(control.name != null) { - _controlByName.remove(control.name); - } - } - - set dirty(value) { - super.dirty = _parentForm.dirty = true; - } - bool get isEmpty => false; bool get isNotEmpty => !isEmpty; get values => null; @@ -166,32 +83,11 @@ class NgForm extends NgControl implements NgDetachAware, Map { putIfAbsent(_, __) => null; } -class NgNullForm implements NgForm { - var _name, _dirty, _valid, _invalid, _pristine, _element; - var _controls, _scope, _parentForm, _controlName; - var errors, _controlByName; - dom.Element element; +class NgNullForm extends NgNullControl implements NgForm { NgNullForm() {} + operator[](name) {} operator []=(String name, value) {} - addControl(control) {} - removeControl(control) {} - setValidity(control, String errorType, bool isValid) {} - - get name => null; - set name(name) {} - - get pristine => null; - set pristine(value) {} - - get dirty => null; - set dirty(value) {} - - get valid => null; - set valid(value) {} - - get invalid => null; - set invalid(value) {} bool get isEmpty => false; bool get isNotEmpty => true; @@ -205,6 +101,4 @@ class NgNullForm implements NgForm { addAll(_) => null; forEach(_) => null; putIfAbsent(_, __) => null; - - detach() => null; } diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index 2eb46f672..b68250769 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -12,40 +12,27 @@ part of angular.directive; */ @NgDirective(selector: '[ng-model]') class NgModel extends NgControl { - final NgForm _form; - final dom.Element _element; - final Scope _scope; - BoundGetter getter = ([_]) => null; BoundSetter setter = (_, [__]) => null; String _exp; - String _name; - final List<_NgModelValidator> _validators = new List<_NgModelValidator>(); - final Map errors = new Map(); - Function _removeWatch = () => null; bool _watchCollection; - + Function _removeWatch = () => null; Function render = (value) => null; - NgModel(this._scope, NodeAttrs attrs, [dom.Element this._element, - NgForm this._form]) { + NgModel(Scope scope, NodeAttrs attrs, dom.Element element, Injector injector) : + super(scope, element, injector) { _exp = 'ng-model=${attrs["ng-model"]}'; watchCollection = false; - - _form.addControl(this); - pristine = true; } - get element => _element; - @NgAttr('name') get name => _name; set name(value) { _name = value; - _form.addControl(this); + _parentControl.addControl(this); } get watchCollection => _watchCollection; @@ -80,7 +67,7 @@ class NgModel extends NgControl { * Executes a validation on the form against each of the validation present on the model. */ validate() { - if(validators.isNotEmpty) { + if (validators.isNotEmpty) { validators.forEach((validator) { setValidity(validator.name, validator.isValid()); }); @@ -89,31 +76,8 @@ class NgModel extends NgControl { } } - /** - * Sets the validity status of the given errorType on the model. Depending on if - * valid or invalid, the matching CSS classes will be added/removed on the input - * element associated with the model. If any errors exist on the model then invalid - * will be set to true otherwise valid will be set to true. - * - * * [errorType] - The name of the error (e.g. required, url, number, etc...). - * * [isValid] - Whether or not the given error is valid or not (false would mean the error is real). - */ - setValidity(String errorType, bool isValid) { - if(isValid) { - if(errors.containsKey(errorType)) { - errors.remove(errorType); - } - if(valid != true && errors.isEmpty) { - valid = true; - } - } else if(!errors.containsKey(errorType)) { - errors[errorType] = true; - invalid = true; - } - - if(_form != null) { - _form.setValidity(this, errorType, isValid); - } + setValidity(String name, bool isValid) { + this.updateControlValidity(this, name, isValid); } /** @@ -131,17 +95,6 @@ class NgModel extends NgControl { validators.remove(v); validate(); } - - set dirty(value) { - super.dirty = _form.dirty = true; - } - - /** - * Removes the model from the control/form. - */ - destroy() { - _form.removeControl(this); - } } /** diff --git a/lib/directive/ng_model_validators.dart b/lib/directive/ng_model_validators.dart index dd19c94fb..dde1d7996 100644 --- a/lib/directive/ng_model_validators.dart +++ b/lib/directive/ng_model_validators.dart @@ -18,7 +18,7 @@ abstract class _NgModelValidator { * Registers the validator with to attached model. */ void listen() { - if(!_listening) { + if (!_listening) { _listening = true; this.ngModel.addValidator(this); } @@ -30,7 +30,7 @@ abstract class _NgModelValidator { * De-registers the validator with to attached model. */ void unlisten() { - if(_listening) { + if (_listening) { _listening = false; this.ngModel.removeValidator(this); } @@ -56,7 +56,7 @@ class NgModelRequiredValidator extends _NgModelValidator { NgModelRequiredValidator(dom.Element inputElement, NgModel ngModel, Scope scope, NodeAttrs attrs): super(inputElement, ngModel, scope) { - if(attrs['required'] != null) required = true; + if (attrs['required'] != null) required = true; } bool isValid() { @@ -134,7 +134,7 @@ class NgModelNumberValidator extends _NgModelValidator { } bool isValid() { - if(value != null) { + if (value != null) { try { num val = double.parse(value.toString()); } catch(exception, stackTrace) { @@ -164,7 +164,7 @@ class NgModelPatternValidator extends _NgModelValidator { } bool isValid() { - if(_pattern != null && value != null && value.length > 0) { + if (_pattern != null && value != null && value.length > 0) { return _pattern.hasMatch(ngModel.viewValue); } @@ -175,7 +175,7 @@ class NgModelPatternValidator extends _NgModelValidator { @NgAttr('pattern') get pattern => _pattern; set pattern(val) { - if(val != null && val.length > 0) { + if (val != null && val.length > 0) { _pattern = new RegExp(val); listen(); } else { @@ -206,7 +206,7 @@ class NgModelMinLengthValidator extends _NgModelValidator { bool isValid() { //remember, only required validates for the input being empty - if(_minlength == 0 || value == null || value.length == 0) { + if (_minlength == 0 || value == null || value.length == 0) { return true; } return value.length >= _minlength; diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index f8d8da0b7..425d763c4 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -93,19 +93,19 @@ describe('form', () { NgModel two = form['two']; NgModel three = form['three']; - form.setValidity(one, "some error", false); + form.updateControlValidity(one, "some error", false); expect(form.valid).toBe(false); expect(form.invalid).toBe(true); - form.setValidity(two, "some error", false); + form.updateControlValidity(two, "some error", false); expect(form.valid).toBe(false); expect(form.invalid).toBe(true); - form.setValidity(one, "some error", true); + form.updateControlValidity(one, "some error", true); expect(form.valid).toBe(false); expect(form.invalid).toBe(true); - form.setValidity(two, "some error", true); + form.updateControlValidity(two, "some error", true); expect(form.valid).toBe(true); expect(form.invalid).toBe(false); })); @@ -121,15 +121,15 @@ describe('form', () { var form = scope['myForm']; NgModel one = form['one']; - form.setValidity(one, "validation error", false); + form.updateControlValidity(one, "validation error", false); expect(form.valid).toBe(false); expect(form.invalid).toBe(true); - form.setValidity(one, "validation error", false); + form.updateControlValidity(one, "validation error", false); expect(form.valid).toBe(false); expect(form.invalid).toBe(true); - form.setValidity(one, "validation error", true); + form.updateControlValidity(one, "validation error", true); expect(form.valid).toBe(true); expect(form.invalid).toBe(false); })); @@ -190,12 +190,12 @@ describe('form', () { expect(fieldset.valid).toBe(true); expect(form.valid).toBe(true); - form.setValidity(fieldset, "error", false); + form.updateControlValidity(fieldset, "error", false); expect(model.valid).toBe(true); expect(fieldset.valid).toBe(true); expect(form.valid).toBe(false); - fieldset.setValidity(model, "error", false); + fieldset.updateControlValidity(model, "error", false); expect(model.valid).toBe(true); expect(fieldset.valid).toBe(false); expect(form.valid).toBe(false); diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index ff1bd27e9..3a71e8f15 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -82,10 +82,10 @@ describe('ng-model', () { expect(inputElement.value).toEqual(''); })); - it('should write to input only if value is different', inject(() { + it('should write to input only if value is different', inject((Injector i) { var scope = _.rootScope; var element = new dom.InputElement(); - var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, new NgNullForm()); + var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, i.createChild([new Module()])); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -147,10 +147,10 @@ describe('ng-model', () { })); - it('should write to input only if value is different', inject(() { + it('should write to input only if value is different', inject((Injector i) { var scope = _.rootScope; var element = new dom.InputElement(); - var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, new NgNullForm()); + var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, i.createChild([new Module()])); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -209,10 +209,10 @@ describe('ng-model', () { expect(_.rootScope.model).toEqual('def'); })); - it('should write to input only if value is different', inject(() { + it('should write to input only if value is different', inject((Injector i) { var scope = _.rootScope; var element = new dom.InputElement(); - var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, new NgNullForm()); + var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, i.createChild([new Module()])); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -279,10 +279,10 @@ describe('ng-model', () { expect(_.rootScope.model).toEqual('def'); })); - it('should write to input only if value is different', inject(() { + it('should write to input only if value is different', inject((Injector i) { var scope = _.rootScope; var element = new dom.InputElement(); - var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, new NgNullForm()); + var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, i.createChild([new Module()])); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -393,10 +393,10 @@ describe('ng-model', () { // NOTE(deboer): This test passes on Dartium, but fails in the content_shell. // The Dart team is looking into this bug. - xit('should write to input only if value is different', inject(() { + xit('should write to input only if value is different', inject((Injector i) { var scope = _.rootScope; var element = new dom.TextAreaElement(); - var model = new NgModel(scope, new NodeAttrs(new DivElement()), element); + var model = new NgModel(scope, new NodeAttrs(new DivElement()), element, i.createChild([new Module()])); dom.querySelector('body').append(element); var input = new InputTextLikeDirective(element, model, scope); @@ -558,21 +558,18 @@ describe('ng-model', () { expect(element.classes.contains('ng-dirty')).toBe(false); })); - it('should render the parent form/fieldset as dirty', inject((Scope scope) { + it('should render the parent form/fieldset as dirty but not the other models', inject((Scope scope) { _.compile('
' + '
' + - ' ' + + ' ' + + ' ' + '
' + '
'); - Probe inputProbe = _.rootScope.i; - var inputElement = inputProbe.element; - - NgForm form = _.rootScope.myForm; - var formElement = form.element; - - NgForm fieldset = _.rootScope.myFieldset; - var fieldsetElement = fieldset.element; + var inputElement1 = _.rootScope.myModel1.element; + var inputElement2 = _.rootScope.myModel2.element; + var formElement = _.rootScope.myForm.element; + var fieldsetElement = _.rootScope.myFieldset.element; expect(formElement.classes.contains('ng-pristine')).toBe(true); expect(formElement.classes.contains('ng-dirty')).toBe(false); @@ -580,11 +577,14 @@ describe('ng-model', () { expect(fieldsetElement.classes.contains('ng-pristine')).toBe(true); expect(fieldsetElement.classes.contains('ng-dirty')).toBe(false); - expect(inputElement.classes.contains('ng-pristine')).toBe(true); - expect(inputElement.classes.contains('ng-dirty')).toBe(false); + expect(inputElement1.classes.contains('ng-pristine')).toBe(true); + expect(inputElement1.classes.contains('ng-dirty')).toBe(false); - inputElement.value = '...hi...'; - _.triggerEvent(inputElement, 'change'); + expect(inputElement2.classes.contains('ng-pristine')).toBe(true); + expect(inputElement2.classes.contains('ng-dirty')).toBe(false); + + inputElement1.value = '...hi...'; + _.triggerEvent(inputElement1, 'change'); expect(formElement.classes.contains('ng-pristine')).toBe(false); expect(formElement.classes.contains('ng-dirty')).toBe(true); @@ -592,8 +592,11 @@ describe('ng-model', () { expect(fieldsetElement.classes.contains('ng-pristine')).toBe(false); expect(fieldsetElement.classes.contains('ng-dirty')).toBe(true); - expect(inputElement.classes.contains('ng-pristine')).toBe(false); - expect(inputElement.classes.contains('ng-dirty')).toBe(true); + expect(inputElement1.classes.contains('ng-pristine')).toBe(false); + expect(inputElement1.classes.contains('ng-dirty')).toBe(true); + + expect(inputElement2.classes.contains('ng-pristine')).toBe(true); + expect(inputElement2.classes.contains('ng-dirty')).toBe(false); })); }); From 0b88d2e8102db8b89f38b00c277b9023b260285e Mon Sep 17 00:00:00 2001 From: "W. Brian Gourlie" Date: Thu, 6 Feb 2014 16:45:18 -0600 Subject: [PATCH 002/102] fix(ngShow): Add/remove ng-hide class instead of ng-show class Closes #521 --- lib/directive/ng_show_hide.dart | 6 ++---- test/core_dom/ng_mustache_spec.dart | 14 +++++++------- test/directive/ng_show_hide_spec.dart | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/lib/directive/ng_show_hide.dart b/lib/directive/ng_show_hide.dart index 68fccfbd1..560cfce76 100644 --- a/lib/directive/ng_show_hide.dart +++ b/lib/directive/ng_show_hide.dart @@ -33,17 +33,15 @@ class NgHideDirective { selector: '[ng-show]', map: const {'ng-show': '=>show'}) class NgShowDirective { - static String NG_SHOW_CLASS = 'ng-show'; - final dom.Element element; NgShowDirective(this.element); set show(value) { if (toBool(value)) { - element.classes.add(NG_SHOW_CLASS); + element.classes.remove(NgHideDirective.NG_HIDE_CLASS); } else { - element.classes.remove(NG_SHOW_CLASS); + element.classes.add(NgHideDirective.NG_HIDE_CLASS); } } } diff --git a/test/core_dom/ng_mustache_spec.dart b/test/core_dom/ng_mustache_spec.dart index 0686d8550..fcd98b5eb 100644 --- a/test/core_dom/ng_mustache_spec.dart +++ b/test/core_dom/ng_mustache_spec.dart @@ -86,39 +86,39 @@ main() { beforeEach(inject((TestBed tb) => _ = tb)); - it('should add/remove ng-show class', () { + it('should add/remove ng-hide class', () { var element = _.compile('
'); - expect(element).not.toHaveClass('ng-show'); + expect(element).not.toHaveClass('ng-hide'); _.rootScope.$apply(() { _.rootScope['isVisible'] = true; }); - expect(element).toHaveClass('ng-show'); + expect(element).not.toHaveClass('ng-hide'); _.rootScope.$apply(() { _.rootScope['isVisible'] = false; }); - expect(element).not.toHaveClass('ng-show'); + expect(element).toHaveClass('ng-hide'); }); it('should work together with ng-class', () { var element = _.compile('
'); expect(element).not.toHaveClass('active'); - expect(element).not.toHaveClass('ng-show'); + expect(element).not.toHaveClass('ng-hide'); _.rootScope.$apply(() { _.rootScope['currentCls'] = 'active'; }); expect(element).toHaveClass('active'); - expect(element).not.toHaveClass('ng-show'); + expect(element).toHaveClass('ng-hide'); _.rootScope.$apply(() { _.rootScope['isVisible'] = true; }); expect(element).toHaveClass('active'); - expect(element).toHaveClass('ng-show'); + expect(element).not.toHaveClass('ng-hide'); }); }); diff --git a/test/directive/ng_show_hide_spec.dart b/test/directive/ng_show_hide_spec.dart index 2cdd0db51..f3e778463 100644 --- a/test/directive/ng_show_hide_spec.dart +++ b/test/directive/ng_show_hide_spec.dart @@ -24,4 +24,25 @@ main() { expect(_.rootElement).not.toHaveClass('ng-hide'); }); }); + + describe('NgShow', () { + TestBed _; + beforeEach(inject((TestBed tb) => _ = tb)); + + it('should add/remove ng-hide class', () { + _.compile('
'); + + expect(_.rootElement).not.toHaveClass('ng-hide'); + + _.rootScope.$apply(() { + _.rootScope['isShown'] = true; + }); + expect(_.rootElement).not.toHaveClass('ng-hide'); + + _.rootScope.$apply(() { + _.rootScope['isShown'] = false; + }); + expect(_.rootElement).toHaveClass('ng-hide'); + }); + }); } From 15e8ef2077be13bd12d84ad82196dcd8adf1db83 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 7 Feb 2014 10:08:31 +0100 Subject: [PATCH 003/102] chore(style): code cleanup fix CS only specify types where strictly necessary to ease readability. Closes #522 --- lib/bootstrap.dart | 17 ++-- lib/core_dom/selector.dart | 149 +++++++++++++++++++------------- lib/introspection.dart | 20 ++--- lib/playback/playback_data.dart | 2 +- lib/playback/playback_http.dart | 17 ++-- lib/routing/ng_bind_route.dart | 13 +-- lib/routing/ng_view.dart | 19 ++-- 7 files changed, 124 insertions(+), 113 deletions(-) diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index 2757135d0..c4d0bf117 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -61,11 +61,11 @@ Injector _defaultInjectorFactory(List modules) => * Injector injector = ngBootstrap(module: myAppModule); */ Injector ngBootstrap({ - Module module: null, - List modules: null, - dom.Element element: null, - String selector: '[ng-app]', - Injector injectorFactory(List modules): _defaultInjectorFactory}) { + Module module: null, + List modules: null, + dom.Element element: null, + String selector: '[ng-app]', + Injector injectorFactory(List modules): _defaultInjectorFactory}) { _publishToJavaScript(); var ngModules = [new AngularModule()]; @@ -74,7 +74,9 @@ Injector ngBootstrap({ if (element == null) { element = dom.querySelector(selector); var document = dom.window.document; - if (element == null) element = document.childNodes.firstWhere((e) => e is dom.Element); + if (element == null) { + element = document.childNodes.firstWhere((e) => e is dom.Element); + } } // The injector must be created inside the zone, so we create the @@ -87,7 +89,8 @@ Injector ngBootstrap({ return zone.run(() { var rootElements = [element]; Injector injector = injectorFactory(ngModules); - injector.get(Compiler)(rootElements, injector.get(DirectiveMap))(injector, rootElements); + injector.get(Compiler)(rootElements, injector.get(DirectiveMap)) + (injector, rootElements); return injector; }); } diff --git a/lib/core_dom/selector.dart b/lib/core_dom/selector.dart index 14579d9b6..83c69ac66 100644 --- a/lib/core_dom/selector.dart +++ b/lib/core_dom/selector.dart @@ -32,7 +32,7 @@ class _Directive { final Type type; final NgAnnotation annotation; - _Directive(Type this.type, NgAnnotation this.annotation); + _Directive(this.type, this.annotation); toString() => annotation.selector; } @@ -42,13 +42,15 @@ class _ContainsSelector { final NgAnnotation annotation; final RegExp regexp; - _ContainsSelector(this.annotation, String regexp) : regexp = new RegExp(regexp); + _ContainsSelector(this.annotation, String regexp) + : regexp = new RegExp(regexp); } -RegExp _SELECTOR_REGEXP = new RegExp(r'^(?:([\w\-]+)|(?:\.([\w\-]+))|(?:\[([\w\-\*]+)(?:=([^\]]*))?\]))'); -RegExp _COMMENT_COMPONENT_REGEXP = new RegExp(r'^\[([\w\-]+)(?:\=(.*))?\]$'); -RegExp _CONTAINS_REGEXP = new RegExp(r'^:contains\(\/(.+)\/\)$'); // -RegExp _ATTR_CONTAINS_REGEXP = new RegExp(r'^\[\*=\/(.+)\/\]$'); // +var _SELECTOR_REGEXP = new RegExp(r'^(?:([\w\-]+)|(?:\.([\w\-]+))|' + r'(?:\[([\w\-\*]+)(?:=([^\]]*))?\]))'); +var _COMMENT_COMPONENT_REGEXP = new RegExp(r'^\[([\w\-]+)(?:\=(.*))?\]$'); +var _CONTAINS_REGEXP = new RegExp(r'^:contains\(\/(.+)\/\)$'); // +var _ATTR_CONTAINS_REGEXP = new RegExp(r'^\[\*=\/(.+)\/\]$'); // class _SelectorPart { final String element; @@ -56,14 +58,14 @@ class _SelectorPart { final String attrName; final String attrValue; - const _SelectorPart.fromElement(String this.element) + const _SelectorPart.fromElement(this.element) : className = null, attrName = null, attrValue = null; - const _SelectorPart.fromClass(String this.className) + const _SelectorPart.fromClass(this.className) : element = null, attrName = null, attrValue = null; - const _SelectorPart.fromAttribute(String this.attrName, String this.attrValue) + const _SelectorPart.fromAttribute(this.attrName, this.attrValue) : element = null, className = null; toString() => @@ -76,16 +78,16 @@ class _SelectorPart { class _ElementSelector { - String name; + final String name; - Map> elementMap = new Map>(); - Map elementPartialMap = new Map(); + var elementMap = >{}; + var elementPartialMap = {}; - Map> classMap = new Map>(); - Map classPartialMap = new Map(); + var classMap = >{}; + var classPartialMap = {}; - Map>> attrValueMap = new Map>>(); - Map> attrValuePartialMap = new Map>(); + var attrValueMap = >>{}; + var attrValuePartialMap = >{}; _ElementSelector(this.name); @@ -96,8 +98,8 @@ class _ElementSelector { if ((name = selectorPart.element) != null) { if (terminal) { elementMap - .putIfAbsent(name, () => []) - .add(directive); + .putIfAbsent(name, () => []) + .add(directive); } else { elementPartialMap .putIfAbsent(name, () => new _ElementSelector(name)) @@ -106,8 +108,8 @@ class _ElementSelector { } else if ((name = selectorPart.className) != null) { if (terminal) { classMap - .putIfAbsent(name, () => []) - .add(directive); + .putIfAbsent(name, () => []) + .add(directive); } else { classPartialMap .putIfAbsent(name, () => new _ElementSelector(name)) @@ -116,13 +118,14 @@ class _ElementSelector { } else if ((name = selectorPart.attrName) != null) { if (terminal) { attrValueMap - .putIfAbsent(name, () => new Map>()) + .putIfAbsent(name, () => >{}) .putIfAbsent(selectorPart.attrValue, () => []) .add(directive); } else { attrValuePartialMap - .putIfAbsent(name, () => new Map()) - .putIfAbsent(selectorPart.attrValue, () => new _ElementSelector(name)) + .putIfAbsent(name, () => {}) + .putIfAbsent(selectorPart.attrValue, () => + new _ElementSelector(name)) .addDirective(selectorParts, directive); } } else { @@ -130,37 +133,47 @@ class _ElementSelector { } } - _addRefs(List refs, List<_Directive> directives, dom.Node node, [String attrValue]) { + _addRefs(List refs, List<_Directive> directives, dom.Node node, + [String attrValue]) { directives.forEach((directive) => - refs.add(new DirectiveRef(node, directive.type, directive.annotation, attrValue))); + refs.add(new DirectiveRef(node, directive.type, directive.annotation, + attrValue))); } - List<_ElementSelector> selectNode(List refs, List<_ElementSelector> partialSelection, - dom.Node node, String nodeName) { + List<_ElementSelector> selectNode(List refs, + List<_ElementSelector> partialSelection, + dom.Node node, String nodeName) { if (elementMap.containsKey(nodeName)) { _addRefs(refs, elementMap[nodeName], node); } if (elementPartialMap.containsKey(nodeName)) { - if (partialSelection == null) partialSelection = new List<_ElementSelector>(); + if (partialSelection == null) { + partialSelection = new List<_ElementSelector>(); + } partialSelection.add(elementPartialMap[nodeName]); } return partialSelection; } - List<_ElementSelector> selectClass(List refs, List<_ElementSelector> partialSelection, - dom.Node node, String className) { + List<_ElementSelector> selectClass(List refs, + List<_ElementSelector> partialSelection, + dom.Node node, String className) { if (classMap.containsKey(className)) { _addRefs(refs, classMap[className], node); } if (classPartialMap.containsKey(className)) { - if (partialSelection == null) partialSelection = new List<_ElementSelector>(); + if (partialSelection == null) { + partialSelection = new List<_ElementSelector>(); + } partialSelection.add(classPartialMap[className]); } return partialSelection; } - List<_ElementSelector> selectAttr(List refs, List<_ElementSelector> partialSelection, - dom.Node node, String attrName, String attrValue) { + List<_ElementSelector> selectAttr(List refs, + List<_ElementSelector> partialSelection, + dom.Node node, String attrName, + String attrValue) { String matchingKey = _matchingKey(attrValueMap.keys, attrName); @@ -174,30 +187,34 @@ class _ElementSelector { } } if (attrValuePartialMap.containsKey(attrName)) { - Map valuesPartialMap = attrValuePartialMap[attrName]; + Map valuesPartialMap = + attrValuePartialMap[attrName]; if (valuesPartialMap.containsKey('')) { - if (partialSelection == null) partialSelection = new List<_ElementSelector>(); + if (partialSelection == null) { + partialSelection = new List<_ElementSelector>(); + } partialSelection.add(valuesPartialMap['']); } if (attrValue != '' && valuesPartialMap.containsKey(attrValue)) { - if (partialSelection == null) partialSelection = new List<_ElementSelector>(); + if (partialSelection == null) { + partialSelection = new List<_ElementSelector>(); + } partialSelection.add(valuesPartialMap[attrValue]); } } return partialSelection; } - String _matchingKey(Iterable keys, String attrName) { - return keys.firstWhere( - (key) => new RegExp('^${key.replaceAll('*', r'[\w\-]+')}\$').hasMatch(attrName), - orElse: () => null); - } + String _matchingKey(Iterable keys, String attrName) => + keys.firstWhere((key) => + new RegExp('^${key.replaceAll('*', r'[\w\-]+')}\$') + .hasMatch(attrName), orElse: () => null); toString() => 'ElementSelector($name)'; } List<_SelectorPart> _splitCss(String selector, Type type) { - List<_SelectorPart> parts = []; + var parts = <_SelectorPart>[]; var remainder = selector; var match; while (!remainder.isEmpty) { @@ -226,9 +243,9 @@ List<_SelectorPart> _splitCss(String selector, Type type) { */ DirectiveSelector directiveSelectorFactory(DirectiveMap directives) { - _ElementSelector elementSelector = new _ElementSelector(''); - List<_ContainsSelector> attrSelector = []; - List<_ContainsSelector> textSelector = []; + var elementSelector = new _ElementSelector(''); + var attrSelector = <_ContainsSelector>[]; + var textSelector = <_ContainsSelector>[]; directives.forEach((NgAnnotation annotation, Type type) { var match; var selector = annotation.selector; @@ -242,17 +259,18 @@ DirectiveSelector directiveSelectorFactory(DirectiveMap directives) { } else if ((match = _ATTR_CONTAINS_REGEXP.firstMatch(selector)) != null) { attrSelector.add(new _ContainsSelector(annotation, match[1])); } else if ((selectorParts = _splitCss(selector, type)) != null){ - elementSelector.addDirective(selectorParts, new _Directive(type, annotation)); + elementSelector.addDirective(selectorParts, + new _Directive(type, annotation)); } else { throw new ArgumentError('Unsupported Selector: $selector'); } }); return (dom.Node node) { - List directiveRefs = []; - List<_ElementSelector> partialSelection = null; - Map classes = new Map(); - Map attrs = new Map(); + var directiveRefs = []; + List<_ElementSelector> partialSelection; + var classes = {}; + var attrs = {}; switch(node.nodeType) { case 1: // Element @@ -266,25 +284,27 @@ DirectiveSelector directiveSelectorFactory(DirectiveMap directives) { } // Select node - partialSelection = elementSelector.selectNode(directiveRefs, partialSelection, element, nodeName); + partialSelection = elementSelector.selectNode(directiveRefs, + partialSelection, element, nodeName); // Select .name if ((element.classes) != null) { for(var name in element.classes) { classes[name] = true; - partialSelection = elementSelector.selectClass(directiveRefs, partialSelection, element, name); + partialSelection = elementSelector.selectClass(directiveRefs, + partialSelection, element, name); } } // Select [attributes] - element.attributes.forEach((attrName, value){ + element.attributes.forEach((attrName, value) { attrs[attrName] = value; - for(var k = 0, kk = attrSelector.length; k < kk; k++) { + for(var k = 0; k < attrSelector.length; k++) { _ContainsSelector selectorRegExp = attrSelector[k]; if (selectorRegExp.regexp.hasMatch(value)) { // this directive is matched on any attribute name, and so - // we need to pass the name to the directive by prefixing it to the - // value. Yes it is a bit of a hack. + // we need to pass the name to the directive by prefixing it to + // the value. Yes it is a bit of a hack. directives[selectorRegExp.annotation].forEach((type) { directiveRefs.add(new DirectiveRef( node, type, selectorRegExp.annotation, '$attrName=$value')); @@ -292,7 +312,8 @@ DirectiveSelector directiveSelectorFactory(DirectiveMap directives) { } } - partialSelection = elementSelector.selectAttr(directiveRefs, partialSelection, node, attrName, value); + partialSelection = elementSelector.selectAttr(directiveRefs, + partialSelection, node, attrName, value); }); while(partialSelection != null) { @@ -300,21 +321,25 @@ DirectiveSelector directiveSelectorFactory(DirectiveMap directives) { partialSelection = null; elementSelectors.forEach((_ElementSelector elementSelector) { classes.forEach((className, _) { - partialSelection = elementSelector.selectClass(directiveRefs, partialSelection, node, className); + partialSelection = elementSelector.selectClass(directiveRefs, + partialSelection, node, className); }); attrs.forEach((attrName, value) { - partialSelection = elementSelector.selectAttr(directiveRefs, partialSelection, node, attrName, value); + partialSelection = elementSelector.selectAttr(directiveRefs, + partialSelection, node, attrName, value); }); }); } break; case 3: // Text Node - for(var value = node.nodeValue, k = 0, kk = textSelector.length; k < kk; k++) { - var selectorRegExp = textSelector[k]; + var value = node.nodeValue; + for(var k = 0; k < textSelector.length; k++) { + var selectorRegExp = textSelector[k]; if (selectorRegExp.regexp.hasMatch(value)) { directives[selectorRegExp.annotation].forEach((type) { - directiveRefs.add(new DirectiveRef(node, type, selectorRegExp.annotation, value)); + directiveRefs.add(new DirectiveRef(node, type, + selectorRegExp.annotation, value)); }); } } diff --git a/lib/introspection.dart b/lib/introspection.dart index 04a20246f..e2d27235a 100644 --- a/lib/introspection.dart +++ b/lib/introspection.dart @@ -4,7 +4,7 @@ part of angular; * A global write only variable which keeps track of objects attached to the elements. * This is usefull for debugging AngularDart application from the browser's REPL. */ -Expando _elementExpando = new Expando('element'); +var _elementExpando = new Expando('element'); /** * Return the closest [ElementProbe] object for a given [Element]. @@ -74,11 +74,12 @@ List ngDirectives(dom.Node node) { } _publishToJavaScript() { - js.context['ngProbe'] = (dom.Node node) => _jsProbe(ngProbe(node)); - js.context['ngInjector'] = (dom.Node node) => _jsInjector(ngInjector(node)); - js.context['ngScope'] = (dom.Node node) => _jsScope(ngScope(node)); - js.context['ngQuery'] = (dom.Node node, String selector, [String containsText]) => - new js.JsArray.from(ngQuery(node, selector, containsText)); + js.context + ..['ngProbe'] = (dom.Node node) => _jsProbe(ngProbe(node)) + ..['ngInjector'] = (dom.Node node) => _jsInjector(ngInjector(node)) + ..['ngScope'] = (dom.Node node) => _jsScope(ngScope(node)) + ..['ngQuery'] = (dom.Node node, String selector, [String containsText]) => + new js.JsArray.from(ngQuery(node, selector, containsText)); } js.JsObject _jsProbe(ElementProbe probe) { @@ -90,11 +91,8 @@ js.JsObject _jsProbe(ElementProbe probe) { })..['_dart_'] = probe; } -js.JsObject _jsInjector(Injector injector) { - return new js.JsObject.jsify({ - "get": injector.get - })..['_dart_'] = injector; -} +js.JsObject _jsInjector(Injector injector) => + new js.JsObject.jsify({ "get": injector.get})..['_dart_'] = injector; js.JsObject _jsScope(Scope scope) { return new js.JsObject.jsify({ diff --git a/lib/playback/playback_data.dart b/lib/playback/playback_data.dart index 5f25f9ce4..f37678abf 100644 --- a/lib/playback/playback_data.dart +++ b/lib/playback/playback_data.dart @@ -3,4 +3,4 @@ library angular.playback.playback_data; // During HTTP playback, this file will be replaced with a file // that has playback data. -Map playbackData = { }; +var playbackData = {}; diff --git a/lib/playback/playback_http.dart b/lib/playback/playback_http.dart index df57b95a2..f6253a3d9 100644 --- a/lib/playback/playback_http.dart +++ b/lib/playback/playback_http.dart @@ -13,9 +13,9 @@ import 'package:angular/playback/playback_data.dart' as playback_data; @NgInjectableService() class PlaybackHttpBackendConfig { requestKey(String url, - {String method, bool withCredentials, String responseType, - String mimeType, Map requestHeaders, sendData, - void onProgress(ProgressEvent e)}) { + {String method, bool withCredentials, String responseType, + String mimeType, Map requestHeaders, sendData, + void onProgress(ProgressEvent e)}) { return JSON.encode({ "url": url, "method": method, @@ -29,7 +29,7 @@ class PlaybackHttpBackendConfig { // the HttpBackend, but it will be implemented by ourselves. class HttpBackendWrapper { HttpBackend backend; - HttpBackendWrapper(HttpBackend this.backend); + HttpBackendWrapper(this.backend); } class RecordingHttpBackend implements HttpBackend { @@ -37,9 +37,8 @@ class RecordingHttpBackend implements HttpBackend { HttpBackend _prodBackend; PlaybackHttpBackendConfig _config; - RecordingHttpBackend(HttpBackendWrapper wrapper, this._config) { - this._prodBackend = wrapper.backend; - } + RecordingHttpBackend(HttpBackendWrapper wrapper, this._config) + : _prodBackend = wrapper.backend; Future request(String url, {String method, bool withCredentials, String responseType, @@ -97,9 +96,7 @@ class PlaybackHttpBackend implements HttpBackend { sendData: sendData, onProgress: onProgress); - if (!data.containsKey(key)) { - throw ["Request is not recorded $key"]; - } + if (!data.containsKey(key)) throw ["Request is not recorded $key"]; var playback = data[key]; return new Future.value( new mock.MockHttpRequest( diff --git a/lib/routing/ng_bind_route.dart b/lib/routing/ng_bind_route.dart index 8ffdef3d4..7305401cf 100644 --- a/lib/routing/ng_bind_route.dart +++ b/lib/routing/ng_bind_route.dart @@ -28,8 +28,7 @@ part of angular.routing; selector: '[ng-bind-route]', map: const { 'ng-bind-route': '@routeName' - } -) + }) class NgBindRouteDirective implements RouteProvider { Router _router; String routeName; @@ -41,13 +40,9 @@ class NgBindRouteDirective implements RouteProvider { /// Returns the parent [RouteProvider]. RouteProvider get _parent => _injector.parent.get(RouteProvider); - Route get route { - if (routeName.startsWith('.')) { - return _parent.route.getRoute(routeName.substring(1)); - } else { - return _router.root.getRoute(routeName); - } - } + Route get route => routeName.startsWith('.') ? + _parent.route.getRoute(routeName.substring(1)) : + _router.root.getRoute(routeName); Map get parameters { var res = {}; diff --git a/lib/routing/ng_view.dart b/lib/routing/ng_view.dart index 74a65a81b..45c905766 100644 --- a/lib/routing/ng_view.dart +++ b/lib/routing/ng_view.dart @@ -59,8 +59,7 @@ part of angular.routing; @NgDirective( selector: 'ng-view', publishTypes: const [RouteProvider], - visibility: NgDirective.CHILDREN_VISIBILITY -) + visibility: NgDirective.CHILDREN_VISIBILITY) class NgViewDirective implements NgDetachAware, RouteProvider { final NgRoutingHelper locationService; final BlockCache blockCache; @@ -75,19 +74,15 @@ class NgViewDirective implements NgDetachAware, RouteProvider { NgViewDirective(this.element, this.blockCache, Injector injector, Router router) : injector = injector, locationService = injector.get(NgRoutingHelper) { RouteProvider routeProvider = injector.parent.get(NgViewDirective); - if (routeProvider != null) { - _route = routeProvider.route.newHandle(); - } else { - _route = router.root.newHandle(); - } + _route = routeProvider != null ? + routeProvider.route.newHandle() : + router.root.newHandle(); locationService._registerPortal(this); _maybeReloadViews(); } void _maybeReloadViews() { - if (_route.isActive) { - locationService._reloadViews(startingFrom: _route); - } + if (_route.isActive) locationService._reloadViews(startingFrom: _route); } detach() { @@ -127,9 +122,7 @@ class NgViewDirective implements NgDetachAware, RouteProvider { } _cleanUp() { - if (_previousBlock == null) { - return; - } + if (_previousBlock == null) return; _previousBlock.remove(); _previousScope.$destroy(); From a33acc0ecf03612d90c74b9de2442e5b1ca2bff6 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 7 Feb 2014 16:25:00 +0100 Subject: [PATCH 004/102] chore(style): code cleanup --- lib/core_dom/http.dart | 235 +++++++++++++++++-------------------- lib/mock/http_backend.dart | 209 ++++++++++++++------------------- 2 files changed, 196 insertions(+), 248 deletions(-) diff --git a/lib/core_dom/http.dart b/lib/core_dom/http.dart index 6adeb2ff4..abcd91c6c 100644 --- a/lib/core_dom/http.dart +++ b/lib/core_dom/http.dart @@ -33,7 +33,7 @@ class HttpBackend { mimeType: mimeType, requestHeaders: requestHeaders, sendData: sendData, - onProgress: onProgress).then((x) => c.complete(x), + onProgress: onProgress).then((x) => c.complete(x), onError: (e, stackTrace) => c.completeError(e, stackTrace)); return c.future; } @@ -62,9 +62,8 @@ class HttpInterceptor { /** * All parameters are optional. */ - HttpInterceptor({ - this.request, this.response, - this.requestError, this.responseError}); + HttpInterceptor({this.request, this.response, this.requestError, + this.responseError}); } @@ -79,7 +78,8 @@ class HttpInterceptor { */ class DefaultTransformDataHttpInterceptor implements HttpInterceptor { Function request = (HttpResponseConfig config) { - if (config.data != null && config.data is! String && config.data is! dom.File) { + if (config.data != null && config.data is! String && + config.data is! dom.File) { config.data = JSON.encode(config.data); } return config; @@ -90,8 +90,7 @@ class DefaultTransformDataHttpInterceptor implements HttpInterceptor { static var _PROTECTION_PREFIX = new RegExp('^\\)\\]\\}\',?\\n'); Function response = (HttpResponse r) { if (r.data is String) { - var d = r.data; - d = d.replaceFirst(_PROTECTION_PREFIX, ''); + var d = r.data.replaceFirst(_PROTECTION_PREFIX, ''); if (d.contains(_JSON_START) && d.contains(_JSON_END)) { d = JSON.decode(d); } @@ -108,7 +107,8 @@ class DefaultTransformDataHttpInterceptor implements HttpInterceptor { */ @NgInjectableService() class HttpInterceptors { - List _interceptors = [new DefaultTransformDataHttpInterceptor()]; + List _interceptors = + [new DefaultTransformDataHttpInterceptor()]; add(HttpInterceptor x) => _interceptors.add(x); addAll(List x) => _interceptors.addAll(x); @@ -119,12 +119,13 @@ class HttpInterceptors { constructChain(List chain) { _interceptors.reversed.forEach((HttpInterceptor i) { // AngularJS has an optimization of not including null interceptors. - chain.insert(0, [ - i.request == null ? (x) => x : i.request, - i.requestError]); - chain.add([ - i.response == null ? (x) => x : i.response, - i.responseError]); + chain + ..insert(0, [ + i.request == null ? (x) => x : i.request, + i.requestError]) + ..add([ + i.response == null ? (x) => x : i.response, + i.responseError]); }); } @@ -136,7 +137,8 @@ class HttpInterceptors { } /** - * Creates a [HttpInterceptors] from a [List]. Does not include the default interceptors. + * Creates a [HttpInterceptors] from a [List]. Does not include the default + * interceptors. */ HttpInterceptors.of([List interceptors]) { _interceptors = interceptors; @@ -228,12 +230,10 @@ class HttpResponse { /** * The response's headers. Without parameters, this method will return the - * [Map] of headers. With [key] parameter, this method will return the specific - * header. + * [Map] of headers. With [key] parameter, this method will return the + * specific header. */ - headers([String key]) { - return key == null ? _headers : _headers[key]; - } + headers([String key]) => key == null ? _headers : _headers[key]; /** * Useful for debugging. @@ -246,20 +246,12 @@ class HttpResponse { */ @NgInjectableService() class HttpDefaultHeaders { - static String _defaultContentType = 'application/json;charset=utf-8'; - Map _headers = { - 'COMMON': { - 'Accept': 'application/json, text/plain, */*' - }, - 'POST' : { - 'Content-Type': _defaultContentType - }, - 'PUT' : { - 'Content-Type': _defaultContentType - }, - 'PATCH' : { - 'Content-Type': _defaultContentType - } + static var _defaultContentType = 'application/json;charset=utf-8'; + var _headers = { + 'COMMON': {'Accept': 'application/json, text/plain, */*'}, + 'POST' : {'Content-Type': _defaultContentType}, + 'PUT' : {'Content-Type': _defaultContentType }, + 'PATCH' : {'Content-Type': _defaultContentType} }; _applyHeaders(method, ucHeaders, headers) { @@ -288,9 +280,7 @@ class HttpDefaultHeaders { * Passing 'common' as [method] will return a Map that contains headers * common to all operations. */ - operator[](method) { - return _headers[method.toUpperCase()]; - } + operator[](method) => _headers[method.toUpperCase()]; } /** @@ -331,8 +321,8 @@ class HttpDefaults { } /** - * The [Http] service facilitates communication with the remote HTTP servers. It - * uses dart:html's [HttpRequest] and provides a number of features on top + * The [Http] service facilitates communication with the remote HTTP servers. + * It uses dart:html's [HttpRequest] and provides a number of features on top * of the core Dart library. * * For unit testing, applications should use the [MockHttpBackend] service. @@ -343,12 +333,12 @@ class HttpDefaults { * * http(method: 'GET', url: '/someUrl') * .then((HttpResponse response) { .. }, - * onError: (HttpRequest request) { .. }); + * onError: (HttpRequest request) { .. }); * * A response status code between 200 and 299 is considered a success status and - * will result in the 'then' being called. Note that if the response is a redirect, - * Dart's [HttpRequest] will transparently follow it, meaning that the error callback will not be - * called for such responses. + * will result in the 'then' being called. Note that if the response is a + * redirect, Dart's [HttpRequest] will transparently follow it, meaning that the + * error callback will not be called for such responses. * * # Shortcut methods * @@ -389,7 +379,7 @@ class HttpDefaults { */ @NgInjectableService() class Http { - Map> _pendingRequests = >{}; + var _pendingRequests = >{}; BrowserCookies _cookies; LocationWrapper _location; UrlRewriter _rewriter; @@ -404,31 +394,27 @@ class Http { /** * Constructor, useful for DI. */ - Http(this._cookies, this._location, this._rewriter, this._backend, this.defaults, this._interceptors); + Http(this._cookies, this._location, this._rewriter, this._backend, + this.defaults, this._interceptors); /** * DEPRECATED */ - async.Future getString(String url, - {bool withCredentials, void onProgress(dom.ProgressEvent e), Cache cache}) { - return request(url, - withCredentials: withCredentials, - onProgress: onProgress, - cache: cache).then((HttpResponse xhr) => xhr.responseText); - } + async.Future getString(String url, {bool withCredentials, + void onProgress(dom.ProgressEvent e), Cache cache}) => + request(url, + withCredentials: withCredentials, + onProgress: onProgress, + cache: cache).then((HttpResponse xhr) => xhr.responseText); /** - * Parse a request URL and determine whether this is a same-origin request as the application document. - * - * @param {string|Uri} requestUrl The url of the request as a string that will be resolved - * or a parsed URL object. - * @returns {boolean} Whether the request is for the same origin as the application document. + * Parse a [requestUrl] and determine whether this is a same-origin request as + * the application document. */ - _urlIsSameOrigin(String requestUrl) { + bool _urlIsSameOrigin(String requestUrl) { Uri originUrl = Uri.parse(_location.location.toString()); Uri parsed = originUrl.resolve(requestUrl); - return (parsed.scheme == originUrl.scheme && - parsed.host == originUrl.host); + return (parsed.scheme == originUrl.scheme && parsed.host == originUrl.host); } /** @@ -468,39 +454,39 @@ class Http { method = method.toUpperCase(); - if (headers == null) { headers = {}; } + if (headers == null) headers = {}; defaults.headers.setHeaders(headers, method); var xsrfValue = _urlIsSameOrigin(url) ? - _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieName] : null; + _cookies[xsrfCookieName != null ? xsrfCookieName : defaults.xsrfCookieName] : + null; if (xsrfValue != null) { - headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] = xsrfValue; + headers[xsrfHeaderName != null ? xsrfHeaderName : defaults.xsrfHeaderName] + = xsrfValue; } // Check for functions in headers - headers.forEach((k,v) { - if (v is Function) { - headers[k] = v(); - } + headers.forEach((k, v) { + if (v is Function) headers[k] = v(); }); var serverRequest = (HttpResponseConfig config) { - assert(config.data == null || config.data is String || config.data is dom.File); + assert(config.data == null || config.data is String || + config.data is dom.File); // Strip content-type if data is undefined if (config.data == null) { new List.from(headers.keys) - .where((h) => h.toUpperCase() == 'CONTENT-TYPE') - .forEach((h) => headers.remove(h)); + .where((h) => h.toUpperCase() == 'CONTENT-TYPE') + .forEach((h) => headers.remove(h)); } - return request( - null, - config: config, - method: method, - sendData: config.data, - requestHeaders: config.headers, - cache: cache); + return request(null, + config: config, + method: method, + sendData: config.data, + requestHeaders: config.headers, + cache: cache); }; var chain = [[serverRequest, null]]; @@ -541,9 +527,9 @@ class Http { interceptors, cache, timeout - }) => call(method: 'GET', url: url, data: data, params: params, headers: headers, - xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName, - interceptors: interceptors, + }) => call(method: 'GET', url: url, data: data, params: params, + headers: headers, xsrfHeaderName: xsrfHeaderName, + xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache, timeout: timeout); /** @@ -559,9 +545,9 @@ class Http { interceptors, cache, timeout - }) => call(method: 'DELETE', url: url, data: data, params: params, headers: headers, - xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName, - interceptors: interceptors, + }) => call(method: 'DELETE', url: url, data: data, params: params, + headers: headers, xsrfHeaderName: xsrfHeaderName, + xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache, timeout: timeout); /** @@ -577,9 +563,9 @@ class Http { interceptors, cache, timeout - }) => call(method: 'HEAD', url: url, data: data, params: params, headers: headers, - xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName, - interceptors: interceptors, + }) => call(method: 'HEAD', url: url, data: data, params: params, + headers: headers, xsrfHeaderName: xsrfHeaderName, + xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache, timeout: timeout); /** @@ -594,9 +580,9 @@ class Http { interceptors, cache, timeout - }) => call(method: 'PUT', url: url, data: data, params: params, headers: headers, - xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName, - interceptors: interceptors, + }) => call(method: 'PUT', url: url, data: data, params: params, + headers: headers, xsrfHeaderName: xsrfHeaderName, + xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache, timeout: timeout); /** @@ -611,9 +597,9 @@ class Http { interceptors, cache, timeout - }) => call(method: 'POST', url: url, data: data, params: params, headers: headers, - xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName, - interceptors: interceptors, + }) => call(method: 'POST', url: url, data: data, params: params, + headers: headers, xsrfHeaderName: xsrfHeaderName, + xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache, timeout: timeout); /** @@ -629,9 +615,9 @@ class Http { interceptors, cache, timeout - }) => call(method: 'JSONP', url: url, data: data, params: params, headers: headers, - xsrfHeaderName: xsrfHeaderName, xsrfCookieName: xsrfCookieName, - interceptors: interceptors, + }) => call(method: 'JSONP', url: url, data: data, params: params, + headers: headers, xsrfHeaderName: xsrfHeaderName, + xsrfCookieName: xsrfCookieName, interceptors: interceptors, cache: cache, timeout: timeout); /** @@ -648,14 +634,10 @@ class Http { var i = line.indexOf(':'); if (i == -1) return; var key = line.substring(0, i).trim().toLowerCase(); - var val = line.substring(i + 1).trim(); - - if (key != '') { - if (parsed.containsKey(key)) { - parsed[key] += ', ' + val; - } else { - parsed[key] = val; - } + + if (key.isNotEmpty) { + var val = line.substring(i + 1).trim(); + parsed[key] = parsed.containsKey(key) ? "${parsed[key]}, $val" : val; } }); return parsed; @@ -666,7 +648,7 @@ class Http { * that the [Http] service is currently waiting for. */ Iterable > get pendingRequests => - _pendingRequests.values; + _pendingRequests.values; /** * DEPRECATED @@ -690,7 +672,7 @@ class Http { url = _buildUrl(config.url, config.params); } - if (cache is bool && cache == false) { + if (cache == false) { cache = null; } else if (cache == null) { cache = defaults.cache; @@ -699,9 +681,11 @@ class Http { if (cache != null && _pendingRequests.containsKey(url)) { return _pendingRequests[url]; } - var cachedValue = (cache != null && method == 'GET') ? cache.get(url) : null; - if (cachedValue != null) { - return new async.Future.value(new HttpResponse.copy(cachedValue)); + var cachedResponse = (cache != null && method == 'GET') + ? cache.get(url) + : null; + if (cachedResponse != null) { + return new async.Future.value(new HttpResponse.copy(cachedResponse)); } var result = _backend.request(url, @@ -715,19 +699,14 @@ class Http { // TODO: Uncomment after apps migrate off of this class. // assert(value.status >= 200 && value.status < 300); - var response = new HttpResponse( - value.status, value.responseText, parseHeaders(value), - config); + var response = new HttpResponse(value.status, value.responseText, + parseHeaders(value), config); - if (cache != null) { - cache.put(url, response); - } + if (cache != null) cache.put(url, response); _pendingRequests.remove(url); return response; }, onError: (error) { - if (error is! dom.ProgressEvent) { - throw error; - } + if (error is! dom.ProgressEvent) throw error; dom.ProgressEvent event = error; _pendingRequests.remove(url); dom.HttpRequest request = event.currentTarget; @@ -735,8 +714,7 @@ class Http { new HttpResponse(request.status, request.response, parseHeaders(request), config)); }); - _pendingRequests[url] = result; - return result; + return _pendingRequests[url] = result; } _buildUrl(String url, Map params) { @@ -749,21 +727,18 @@ class Http { if (value is! List) value = [value]; value.forEach((v) { - if (v is Map) { - v = JSON.encode(v); - } - parts.add(_encodeUriQuery(key) + '=' + - _encodeUriQuery("$v")); + if (v is Map) v = JSON.encode(v); + parts.add(_encodeUriQuery(key) + '=' + _encodeUriQuery("$v")); }); }); return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); } _encodeUriQuery(val, {bool pctEncodeSpaces: false}) => - Uri.encodeComponent(val) - .replaceAll('%40', '@') - .replaceAll('%3A', ':') - .replaceAll('%24', r'$') - .replaceAll('%2C', ',') - .replaceAll('%20', pctEncodeSpaces ? '%20' : '+'); + Uri.encodeComponent(val) + .replaceAll('%40', '@') + .replaceAll('%3A', ':') + .replaceAll('%24', r'$') + .replaceAll('%2C', ',') + .replaceAll('%20', pctEncodeSpaces ? '%20' : '+'); } diff --git a/lib/mock/http_backend.dart b/lib/mock/http_backend.dart index b7a5623c0..a6f19bc0d 100644 --- a/lib/mock/http_backend.dart +++ b/lib/mock/http_backend.dart @@ -3,7 +3,7 @@ part of angular.mock; class _MockXhr { var $$method, $$url, $$async, $$reqHeaders, $$respHeaders; - open(method, url, async) { + void open(method, url, async) { $$method = method; $$url = url; $$async = async; @@ -13,24 +13,21 @@ class _MockXhr { var $$data; - send(data) { + void send(data) { $$data = data; } - setRequestHeader(key, value) { + void setRequestHeader(key, value) { $$reqHeaders[key] = value; } - getResponseHeader(name) { - // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last - if ($$respHeaders.containsKey(name)) { - return $$respHeaders[name]; - } + String getResponseHeader(name) { + // the lookup must be case insensitive, that's why we try two quick + // lookups and full scan at last + if ($$respHeaders.containsKey(name)) return $$respHeaders[name]; name = name.toLowerCase(); - if ($$respHeaders.containsKey(name)) { - return $$respHeaders[name]; - } + if ($$respHeaders.containsKey(name)) return $$respHeaders[name]; String header = null; $$respHeaders.forEach((headerName, headerVal) { @@ -59,53 +56,51 @@ class _MockXhr { * An internal class used by [MockHttpBackend]. */ class MockHttpExpectation { - - var method, url, data, headers; + final method; + final url; + final data; + final headers; var response; MockHttpExpectation(this.method, this.url, [this.data, this.headers]); - match(m, u, [d, h]) { - if (method != m) return false; - if (!matchUrl(u)) return false; - if (d != null && !matchData(d)) return false; - if (h != null && !matchHeaders(h)) return false; + bool match(method, url, [data, headers]) { + if (method != method) return false; + if (!matchUrl(url)) return false; + if (data != null && !matchData(data)) return false; + if (headers != null && !matchHeaders(headers)) return false; return true; } - matchUrl(u) { + bool matchUrl(u) { if (url == null) return true; if (url is RegExp) return url.hasMatch(u); return url == u; } - matchHeaders(h) { + bool matchHeaders(h) { if (headers == null) return true; if (headers is Function) return headers(h); return "$headers" == "$h"; } - matchData(d) { + bool matchData(d) { if (data == null) return true; - if (d == null) return false; // data is not null, but d is. + if (d == null) return false; if (data is File) return data == d; assert(d is String); if (data is RegExp) return data.hasMatch(d); return JSON.encode(data) == JSON.encode(d); } - toString() { - return "$method $url"; - } + String toString() => "$method $url"; } class _Chain { - var _respondFn; - _Chain({respond}) { - _respondFn = respond; - } + final _respondFn; + _Chain({respond}): _respondFn = respond; respond([x,y,z]) => _respondFn(x,y,z); } @@ -129,12 +124,12 @@ class MockHttpBackend implements HttpBackend { if (status >= 200 && status < 300) { c.complete(new MockHttpRequest(status, data, headers)); } else { - c.completeError( - new MockProgressEvent( - new MockHttpRequest(status, data, headers))); + c.completeError(new MockProgressEvent( + new MockHttpRequest(status, data, headers))); } }; - call(method == null ? 'GET' : method, url, sendData, callback, requestHeaders); + call(method == null ? 'GET' : method, url, sendData, callback, + requestHeaders); return c.future; } @@ -150,9 +145,7 @@ class MockHttpBackend implements HttpBackend { data = statusOrDataOrFunction; headers = dataOrHeaders; } - if (data is Map || data is List) { - data = JSON.encode(data); - } + if (data is Map || data is List) data = JSON.encode(data); return ([a,b,c,d,e]) => [status, data, headers]; } @@ -162,7 +155,7 @@ class MockHttpBackend implements HttpBackend { * A callback oriented API. This function takes a callback with * will be called with (status, data, headers) */ - call(method, [url, data, callback, headers, timeout]) { + void call(method, [url, data, callback, headers, timeout]) { var xhr = new _MockXhr(), expectation = expectations.isEmpty ? null : expectations[0], wasExpected = false; @@ -177,11 +170,12 @@ class MockHttpBackend implements HttpBackend { var handleResponse = () { var response = wrapped.response(method, url, data, headers); xhr.$$respHeaders = response[2]; - utils.relaxFnApply(callback, [response[0], response[1], xhr.getAllResponseHeaders()]); + utils.relaxFnApply(callback, [response[0], response[1], + xhr.getAllResponseHeaders()]); }; var handleTimeout = () { - for (var i = 0, ii = responses.length; i < ii; i++) { + for (var i = 0; i < responses.length; i++) { if (identical(responses[i], handleResponse)) { responses.removeAt(i); callback(-1, null, ''); @@ -200,8 +194,9 @@ class MockHttpBackend implements HttpBackend { 'EXPECTED: ${prettyPrint(expectation.data)}\nGOT: $data']; if (!expectation.matchHeaders(headers)) - throw ['Expected $expectation with different headers\n' + - 'EXPECTED: ${prettyPrint(expectation.headers)}\nGOT: ${prettyPrint(headers)}']; + throw ['Expected $expectation with different headers\n' + 'EXPECTED: ${prettyPrint(expectation.headers)}\n' + 'GOT: ${prettyPrint(headers)}']; expectations.removeAt(0); @@ -223,8 +218,9 @@ class MockHttpBackend implements HttpBackend { } throw wasExpected ? ['No response defined !'] : - ['Unexpected request: $method $url\n' + - (expectation != null ? 'Expected $expectation' : 'No more requests expected')]; + ['Unexpected request: $method $url\n' + (expectation != null ? + 'Expected $expectation' : + 'No more requests expected')]; } /** @@ -233,21 +229,22 @@ class MockHttpBackend implements HttpBackend { * @param {string} method HTTP method. * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current definition. - * @returns {requestHandler} Returns an object with `respond` method that control how a matched - * request is handled. + * @param {(Object|function(Object))=} headers HTTP headers or function that + * receives http header object and returns true if the headers match the + * current definition. + * @returns {requestHandler} Returns an object with `respond` method that + * control how a matched request is handled. * * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` * – The respond method takes a set of static data to be returned or a function that can return * an array containing response status (number), response data (string) and response headers * (Object). */ - when(method, [url, data, headers]) { + _Chain when(method, [url, data, headers]) { var definition = new MockHttpExpectation(method, url, data, headers), chain = new _Chain(respond: (status, data, headers) { - definition.response = _createResponse(status, data, headers); - }); + definition.response = _createResponse(status, data, headers); + }); definitions.add(definition); return chain; @@ -265,54 +262,41 @@ class MockHttpBackend implements HttpBackend { * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ - - - whenGET(url, [headers]) => - when('GET', url, null, headers); - whenDELETE(url, [headers]) => - when('DELETE', url, null, headers); - whenJSONP(url, [headers]) => - when('JSONP', url, null, headers); - - whenPUT(url, [data, headers]) => - when('PUT', url, data, headers); - whenPOST(url, [data, headers]) => - when('POST', url, data, headers); - whenPATCH(url, [data, headers]) => - when('PATCH', url, data, headers); + _Chain whenGET(url, [headers]) => when('GET', url, null, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#whenHEAD + * @name ngMock.$httpBackend#whenDELETE * @methodOf ngMock.$httpBackend * @description - * Creates a new backend definition for HEAD requests. For more info see `when()`. + * Creates a new backend definition for DELETE requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain whenDELETE(url, [headers]) => when('DELETE', url, null, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#whenDELETE + * @name ngMock.$httpBackend#whenJSONP * @methodOf ngMock.$httpBackend * @description - * Creates a new backend definition for DELETE requests. For more info see `when()`. + * Creates a new backend definition for JSONP requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. - * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain whenJSONP(url, [headers]) => when('JSONP', url, null, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#whenPOST + * @name ngMock.$httpBackend#whenPUT * @methodOf ngMock.$httpBackend * @description - * Creates a new backend definition for POST requests. For more info see `when()`. + * Creates a new backend definition for PUT requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. @@ -320,13 +304,14 @@ class MockHttpBackend implements HttpBackend { * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain whenPUT(url, [data, headers]) => when('PUT', url, data, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#whenPUT + * @name ngMock.$httpBackend#whenPOST * @methodOf ngMock.$httpBackend * @description - * Creates a new backend definition for PUT requests. For more info see `when()`. + * Creates a new backend definition for POST requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. @@ -334,20 +319,22 @@ class MockHttpBackend implements HttpBackend { * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain whenPOST(url, [data, headers]) => when('POST', url, data, headers); + + _Chain whenPATCH(url, [data, headers]) => when('PATCH', url, data, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#whenJSONP + * @name ngMock.$httpBackend#whenHEAD * @methodOf ngMock.$httpBackend * @description - * Creates a new backend definition for JSONP requests. For more info see `when()`. + * Creates a new backend definition for HEAD requests. For more info see `when()`. * * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ - //createShortMethods('when'); - /** * @ngdoc method @@ -369,12 +356,12 @@ class MockHttpBackend implements HttpBackend { * an array containing response status (number), response data (string) and response headers * (Object). */ - expect(method, [url, data, headers]) { + _Chain expect(method, [url, data, headers]) { var expectation = new MockHttpExpectation(method, url, data, headers); expectations.add(expectation); return new _Chain(respond: (status, data, headers) { - expectation.response = _createResponse(status, data, headers); - }); + expectation.response = _createResponse(status, data, headers); + }); } @@ -390,52 +377,41 @@ class MockHttpBackend implements HttpBackend { * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. See #expect for more info. */ - expectGET(url, [headers]) => - expect('GET', url, null, headers); - expectDELETE(url, [headers]) => - expect('DELETE', url, null, headers); - expectJSONP(url, [headers]) => - expect('JSONP', url, null, headers); - - expectPUT(url, [data, headers]) => - expect('PUT', url, data, headers); - expectPOST(url, [data, headers]) => - expect('POST', url, data, headers); - expectPATCH(url, [data, headers]) => - expect('PATCH', url, data, headers); + _Chain expectGET(url, [headers]) => expect('GET', url, null, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#expectHEAD + * @name ngMock.$httpBackend#expectDELETE * @methodOf ngMock.$httpBackend * @description - * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * Creates a new request expectation for DELETE requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain expectDELETE(url, [headers]) => expect('DELETE', url, null, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#expectDELETE + * @name ngMock.$httpBackend#expectJSONP * @methodOf ngMock.$httpBackend * @description - * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * Creates a new request expectation for JSONP requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. - * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain expectJSONP(url, [headers]) => expect('JSONP', url, null, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#expectPOST + * @name ngMock.$httpBackend#expectPUT * @methodOf ngMock.$httpBackend * @description - * Creates a new request expectation for POST requests. For more info see `expect()`. + * Creates a new request expectation for PUT requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. @@ -443,13 +419,14 @@ class MockHttpBackend implements HttpBackend { * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain expectPUT(url, [data, headers]) => expect('PUT', url, data, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#expectPUT + * @name ngMock.$httpBackend#expectPOST * @methodOf ngMock.$httpBackend * @description - * Creates a new request expectation for PUT requests. For more info see `expect()`. + * Creates a new request expectation for POST requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. * @param {(string|RegExp)=} data HTTP request body. @@ -457,6 +434,7 @@ class MockHttpBackend implements HttpBackend { * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain expectPOST(url, [data, headers]) => expect('POST', url, data, headers); /** * @ngdoc method @@ -471,20 +449,20 @@ class MockHttpBackend implements HttpBackend { * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ + _Chain expectPATCH(url, [data, headers]) => expect('PATCH', url, data, headers); /** * @ngdoc method - * @name ngMock.$httpBackend#expectJSONP + * @name ngMock.$httpBackend#expectHEAD * @methodOf ngMock.$httpBackend * @description - * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * Creates a new request expectation for HEAD requests. For more info see `expect()`. * * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. * @returns {requestHandler} Returns an object with `respond` method that control how a matched * request is handled. */ - //createShortMethods('expect'); - /** * @ngdoc method @@ -497,7 +475,7 @@ class MockHttpBackend implements HttpBackend { * all pending requests will be flushed. If there are no pending requests when the flush method * is called an exception is thrown (as this typically a sign of programming error). */ - flush([count]) { + void flush([count]) { if (responses.isEmpty) throw ['No pending request to flush !']; if (count != null) { @@ -529,7 +507,7 @@ class MockHttpBackend implements HttpBackend { * afterEach($httpBackend.verifyNoOutstandingExpectation); * */ - verifyNoOutstandingExpectation() { + void verifyNoOutstandingExpectation() { if (!expectations.isEmpty) { throw ['Unsatisfied requests: ${expectations.join(', ')}']; } @@ -550,10 +528,8 @@ class MockHttpBackend implements HttpBackend { * afterEach($httpBackend.verifyNoOutstandingRequest); * */ - verifyNoOutstandingRequest() { - if (!responses.isEmpty) { - throw ['Unflushed requests: ${responses.length}']; - } + void verifyNoOutstandingRequest() { + if (!responses.isEmpty) throw ['Unflushed requests: ${responses.length}']; } @@ -566,7 +542,7 @@ class MockHttpBackend implements HttpBackend { * call resetExpectations during a multiple-phase test when you want to reuse the same instance of * $httpBackend mock. */ - resetExpectations() { + void resetExpectations() { expectations.length = 0; responses.length = 0; } @@ -611,10 +587,7 @@ class MockHttpRequest implements HttpRequest { void abort() {} bool dispatchEvent(Event event) => false; - String getAllResponseHeaders() { - if (headers == null) return null; - return headers; - } + String getAllResponseHeaders() => headers; String getResponseHeader(String header) => null; void open(String method, String url, {bool async, String user, String password}) {} From 06d6f28541df2dd9d12e2dc25d08d515f904f168 Mon Sep 17 00:00:00 2001 From: Victor Date: Fri, 7 Feb 2014 16:34:37 +0100 Subject: [PATCH 005/102] refactor(http): remove a now useless workaround dartbug.com/13051 has been fixed. see https://code.google.com/p/dart/issues/detail?id=13051 for details. Closes #523 --- lib/core_dom/http.dart | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/core_dom/http.dart b/lib/core_dom/http.dart index abcd91c6c..15f44dbc8 100644 --- a/lib/core_dom/http.dart +++ b/lib/core_dom/http.dart @@ -22,21 +22,11 @@ class HttpBackend { async.Future request(String url, {String method, bool withCredentials, String responseType, String mimeType, Map requestHeaders, sendData, - void onProgress(dom.ProgressEvent e)}) { - // Complete inside a then to work-around dartbug.com/13051 - var c = new async.Completer(); - - dom.HttpRequest.request(url, - method: method, - withCredentials: withCredentials, - responseType: responseType, - mimeType: mimeType, - requestHeaders: requestHeaders, - sendData: sendData, - onProgress: onProgress).then((x) => c.complete(x), - onError: (e, stackTrace) => c.completeError(e, stackTrace)); - return c.future; - } + void onProgress(dom.ProgressEvent e)}) => + dom.HttpRequest.request(url, method: method, + withCredentials: withCredentials, responseType: responseType, + mimeType: mimeType, requestHeaders: requestHeaders, + sendData: sendData, onProgress: onProgress); } @NgInjectableService() From e703bd1bc75f4d6420afad0bbb975b3e23672ff8 Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Sat, 8 Feb 2014 10:49:26 +0200 Subject: [PATCH 006/102] feat(ngModel): Treat the values of number and range inputs as numbers Add a InputNumberLikeDirective that binds the model as a numeric value, so that it can be bound to num types on scopes. Select the directive for number and range input types. Closes #527 --- lib/directive/module.dart | 1 + lib/directive/ng_model.dart | 41 +++++++++++++- test/directive/ng_model_spec.dart | 89 +++++++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 6 deletions(-) diff --git a/lib/directive/module.dart b/lib/directive/module.dart index e92ab8ceb..102e3406a 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -52,6 +52,7 @@ class NgDirectiveModule extends Module { value(NgShalowRepeatDirective, null); value(NgShowDirective, null); value(InputTextLikeDirective, null); + value(InputNumberLikeDirective, null); value(InputRadioDirective, null); value(InputCheckboxDirective, null); value(InputSelectDirective, null); diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index b68250769..9e9d4c5c0 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -130,7 +130,7 @@ class InputCheckboxDirective { /** * Usage: * - * + * * * * This creates a two-way binding between any string-based input element @@ -145,7 +145,6 @@ class InputCheckboxDirective { @NgDirective(selector: 'input[type=password][ng-model]') @NgDirective(selector: 'input[type=url][ng-model]') @NgDirective(selector: 'input[type=email][ng-model]') -@NgDirective(selector: 'input[type=number][ng-model]') @NgDirective(selector: 'input[type=search][ng-model]') class InputTextLikeDirective { final dom.Element inputElement; @@ -185,6 +184,44 @@ class InputTextLikeDirective { } } +/** + * Usage: + * + * + * + * This creates a two-way binding between a number-based input element + * so long as the ng-model attribute is present on the input element. Whenever + * the value of the input element changes then the matching model property on the + * scope will be updated as well as the other way around (when the scope property + * is updated). + * + */ +@NgDirective(selector: 'input[type=number][ng-model]') +@NgDirective(selector: 'input[type=range][ng-model]') +class InputNumberLikeDirective { + final dom.InputElement inputElement; + final NgModel ngModel; + final Scope scope; + + InputNumberLikeDirective(dom.Element this.inputElement, this.ngModel, this.scope) { + ngModel.render = (value) { + inputElement.value = value == null ? '' : value.toString(); + }; + inputElement + ..onChange.listen(relaxFnArgs(processValue)) + ..onInput.listen(relaxFnArgs(processValue)); + } + + processValue() { + var value = num.parse(inputElement.value, (_) => null); + if (value != ngModel.viewValue) { + ngModel.dirty = true; + scope.$apply(() => ngModel.viewValue = value); + } + ngModel.validate(); + } +} + class _UidCounter { static final int CHAR_0 = "0".codeUnitAt(0); static final int CHAR_9 = "9".codeUnitAt(0); diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index 3a71e8f15..8445713a2 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -59,12 +59,12 @@ describe('ng-model', () { inputElement.value = '12'; _.triggerEvent(inputElement, 'change'); - expect(_.rootScope.model).toEqual('12'); + expect(_.rootScope.model).toEqual(12); inputElement.value = '14'; - var input = probe.directive(InputTextLikeDirective); + var input = probe.directive(InputNumberLikeDirective); input.processValue(); - expect(_.rootScope.model).toEqual('14'); + expect(_.rootScope.model).toEqual(14); })); it('should update input type=number to blank when model is null', inject(() { @@ -75,7 +75,7 @@ describe('ng-model', () { inputElement.value = '12'; _.triggerEvent(inputElement, 'change'); - expect(_.rootScope.model).toEqual('12'); + expect(_.rootScope.model).toEqual(12); _.rootScope.model = null; _.rootScope.$apply(); @@ -109,6 +109,87 @@ describe('ng-model', () { })); }); + describe('type="number" like', () { + it('should update input value from model', inject(() { + _.compile(''); + _.rootScope.$digest(); + + _.rootScope.$apply('model = 42'); + expect((_.rootElement as dom.InputElement).value).toEqual('42'); + })); + + it('should update input value from model for range inputs', inject(() { + _.compile(''); + _.rootScope.$digest(); + + _.rootScope.$apply('model = 42'); + expect((_.rootElement as dom.InputElement).value).toEqual('42'); + })); + + it('should update model from the input value', inject(() { + _.compile(''); + Probe probe = _.rootScope.p; + var ngModel = probe.directive(NgModel); + InputElement inputElement = probe.element; + + inputElement.value = '42'; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.model).toEqual(42); + + inputElement.value = '43'; + var input = probe.directive(InputNumberLikeDirective); + input.processValue(); + expect(_.rootScope.model).toEqual(43); + })); + + it('should update model to null from a blank input value', inject(() { + _.compile(''); + Probe probe = _.rootScope.p; + var ngModel = probe.directive(NgModel); + InputElement inputElement = probe.element; + + inputElement.value = ''; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.model).toBeNull(); + })); + + it('should update model from the input value for range inputs', inject(() { + _.compile(''); + Probe probe = _.rootScope.p; + var ngModel = probe.directive(NgModel); + InputElement inputElement = probe.element; + + inputElement.value = '42'; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.model).toEqual(42); + + inputElement.value = '43'; + var input = probe.directive(InputNumberLikeDirective); + input.processValue(); + expect(_.rootScope.model).toEqual(43); + })); + + it('should update model to a native default value from a blank range input value', inject(() { + _.compile(''); + Probe probe = _.rootScope.p; + var ngModel = probe.directive(NgModel); + InputElement inputElement = probe.element; + + inputElement.value = ''; + _.triggerEvent(inputElement, 'change'); + expect(_.rootScope.model).toBeDefined(); + })); + + it('should render null as blank', inject(() { + _.compile(''); + _.rootScope.$digest(); + + _.rootScope.$apply('model = null'); + expect((_.rootElement as dom.InputElement).value).toEqual(''); + })); + + }); + describe('type="password"', () { it('should update input value from model', inject(() { _.compile(''); From 3f22b4658f5bd59064987864ce8f9a6a0188e1d6 Mon Sep 17 00:00:00 2001 From: "W. Brian Gourlie" Date: Mon, 10 Feb 2014 19:54:03 -0600 Subject: [PATCH 007/102] test(NgForm): Regression test for #459. Closes #459 Closes #538 --- test/directive/ng_form_spec.dart | 62 +++++++++++++++++++------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index 425d763c4..1a082f713 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -3,12 +3,10 @@ library form_spec; import '../_specs.dart'; main() => -describe('form', () { +ddescribe('form', () { TestBed _; - beforeEach(inject((TestBed tb) => _ = tb)); - - it('should set the name of the form and attach it to the scope', inject((Scope scope) { + it('should set the name of the form and attach it to the scope', inject((Scope scope, TestBed _) { var element = $('
'); expect(scope['myForm']).toBeNull(); @@ -23,7 +21,7 @@ describe('form', () { })); describe('pristine / dirty', () { - it('should be set to pristine by default', inject((Scope scope) { + it('should be set to pristine by default', inject((Scope scope, TestBed _) { var element = $('
'); _.compile(element); @@ -34,7 +32,7 @@ describe('form', () { expect(form.dirty).toEqual(false); })); - it('should add and remove the correct CSS classes when set to dirty and to pristine', inject((Scope scope) { + it('should add and remove the correct CSS classes when set to dirty and to pristine', inject((Scope scope, TestBed _) { var element = $('
'); _.compile(element); @@ -57,7 +55,7 @@ describe('form', () { }); describe('valid / invalid', () { - it('should add and remove the correct flags when set to valid and to invalid', inject((Scope scope) { + it('should add and remove the correct flags when set to valid and to invalid', inject((Scope scope, TestBed _) { var element = $('
'); _.compile(element); @@ -78,8 +76,8 @@ describe('form', () { expect(element.hasClass('ng-valid')).toBe(true); })); - it('should set the validity with respect to all existing validations when setValidity() is used', inject((Scope scope) { - var element = $('
' + + it('should set the validity with respect to all existing validations when setValidity() is used', inject((Scope scope, TestBed _) { + var element = $('' ' ' + ' ' + ' ' + @@ -110,8 +108,8 @@ describe('form', () { expect(form.invalid).toBe(false); })); - it('should not handle the control + errorType pair more than once', inject((Scope scope) { - var element = $('' + + it('should not handle the control errorType pair more than once', inject((Scope scope, TestBed _) { + var element = $('' ' ' + '
'); @@ -134,8 +132,8 @@ describe('form', () { expect(form.invalid).toBe(false); })); - it('should update the validity of the parent form when the inner model changes', inject((Scope scope) { - var element = $('
' + + it('should update the validity of the parent form when the inner model changes', inject((Scope scope, TestBed _) { + var element = $('' ' ' + ' ' + '
'); @@ -164,8 +162,8 @@ describe('form', () { expect(form.invalid).toBe(false); })); - it('should set the validity for the parent form when fieldsets are used', inject((Scope scope) { - var element = $('
' + + it('should set the validity for the parent form when fieldsets are used', inject((Scope scope, TestBed _) { + var element = $('' '
' + ' ' + '
' + @@ -203,11 +201,11 @@ describe('form', () { }); describe('controls', () { - it('should add each contained ng-model as a control upon compile', inject((Scope scope) { - var element = $('' + + it('should add each contained ng-model as a control upon compile', inject((Scope scope, TestBed _) { + var element = $('' ' ' + ' ' + '
'); @@ -222,8 +220,8 @@ describe('form', () { expect(form['fire_name'].modelValue).toBe('fire'); })); - it('should properly remove controls directly from the ngForm instance', inject((Scope scope) { - var element = $('
' + + it('should properly remove controls directly from the ngForm instance', inject((Scope scope, TestBed _) { + var element = $('' ' ' + '
'); @@ -236,9 +234,9 @@ describe('form', () { expect(form['mega_control']).toBeNull(); })); - it('should remove all controls when the scope is destroyed', inject((Scope scope) { + it('should remove all controls when the scope is destroyed', inject((Scope scope, TestBed _) { Scope childScope = scope.$new(); - var element = $('
' + + var element = $('' ' ' + ' ' + ' ' + @@ -261,7 +259,7 @@ describe('form', () { }); describe('onSubmit', () { - it('should suppress the submission event if no action is provided within the form', inject((Scope scope) { + it('should suppress the submission event if no action is provided within the form', inject((Scope scope, TestBed _) { var element = $('
'); _.compile(element); @@ -280,7 +278,7 @@ describe('form', () { expect(fakeEvent.defaultPrevented).toBe(false); })); - it('should not prevent the submission event if an action is defined', inject((Scope scope) { + it('should not prevent the submission event if an action is defined', inject((Scope scope, TestBed _) { var element = $('
'); _.compile(element); @@ -293,7 +291,7 @@ describe('form', () { expect(submissionEvent.defaultPrevented).toBe(false); })); - it('should execute the ng-submit expression if provided upon form submission', inject((Scope scope) { + it('should execute the ng-submit expression if provided upon form submission', inject((Scope scope, TestBed _) { var element = $('
'); _.compile(element); @@ -307,4 +305,18 @@ describe('form', () { expect(_.rootScope.submitted).toBe(true); })); }); + + describe('regression tests: form', () { + it('should be resolvable by injector if configured by user.', () { + module((Module module) { + module.type(NgForm); + }); + + inject((Injector injector, Compiler compiler, DirectiveMap directives) { + var element = $('
'); + compiler(element, directives)(injector, element); + // The only expectation is that this doesn't throw + }); + }); + }); }); From c1646dc529867dad1a93e35afdcc648986ca6b68 Mon Sep 17 00:00:00 2001 From: Kasper Lund Date: Tue, 11 Feb 2014 09:54:32 +0100 Subject: [PATCH 008/102] chore(cleanup_todo): Clean-up the TODO sample. --- demo/todo/pubspec.lock | 10 +++++----- demo/todo/web/main.dart | 9 ++++----- demo/todo/web/todo.dart | 8 -------- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/demo/todo/pubspec.lock b/demo/todo/pubspec.lock index dca084f5b..927673dc7 100644 --- a/demo/todo/pubspec.lock +++ b/demo/todo/pubspec.lock @@ -10,7 +10,7 @@ packages: path: "../.." relative: true source: path - version: "0.9.4" + version: "0.9.7" args: description: args source: hosted @@ -50,11 +50,11 @@ packages: route_hierarchical: description: route_hierarchical source: hosted - version: "0.4.10" + version: "0.4.13" shadow_dom: description: shadow_dom source: hosted - version: "0.9.1" + version: "0.9.2" source_maps: description: source_maps source: hosted @@ -66,11 +66,11 @@ packages: unittest: description: unittest source: hosted - version: "0.9.3" + version: "0.10.0" unmodifiable_collection: description: unmodifiable_collection source: hosted - version: "0.9.2" + version: "0.9.2+1" utf: description: utf source: hosted diff --git a/demo/todo/web/main.dart b/demo/todo/web/main.dart index 5c67d8084..782430d60 100644 --- a/demo/todo/web/main.dart +++ b/demo/todo/web/main.dart @@ -7,16 +7,15 @@ import 'dart:html'; // Everything in the 'todo' library should be preserved by MirrorsUsed @MirrorsUsed( - targets: const['todo'], + targets: const ['todo'], override: '*') import 'dart:mirrors'; main() { - print(window.location.search); var module = new Module() - ..type(TodoController) - ..type(PlaybackHttpBackendConfig); + ..type(TodoController) + ..type(PlaybackHttpBackendConfig); // If these is a query in the URL, use the server-backed // TodoController. Otherwise, use the stored-data controller. @@ -39,5 +38,5 @@ main() { module.type(HttpBackend, implementedBy: PlaybackHttpBackend); } - ngBootstrap(module:module); + ngBootstrap(module: module); } diff --git a/demo/todo/web/todo.dart b/demo/todo/web/todo.dart index d287be25c..6b535f93c 100644 --- a/demo/todo/web/todo.dart +++ b/demo/todo/web/todo.dart @@ -68,14 +68,6 @@ class TodoController { serverController.init(this); } - // workaround for https://github.com/angular/angular.dart/issues/37 - dynamic operator [](String key) { - if (key == 'newItem') { - return newItem; - } - return null; - } - add() { if (newItem.isEmpty) return; From f7b1cf5b530ee1ac1c6aff7a5f4593d8d1b65c79 Mon Sep 17 00:00:00 2001 From: Kasper Lund Date: Tue, 11 Feb 2014 10:06:35 +0100 Subject: [PATCH 009/102] chore(comments): Terminate comment with period. Closes #539 --- demo/todo/web/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/todo/web/main.dart b/demo/todo/web/main.dart index 782430d60..3f34f0a85 100644 --- a/demo/todo/web/main.dart +++ b/demo/todo/web/main.dart @@ -5,7 +5,7 @@ import 'todo.dart'; import 'dart:html'; -// Everything in the 'todo' library should be preserved by MirrorsUsed +// Everything in the 'todo' library should be preserved by MirrorsUsed. @MirrorsUsed( targets: const ['todo'], override: '*') From d099db5944e2287fbf97a13b1aa73f8082652e09 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Tue, 11 Feb 2014 13:08:49 -0600 Subject: [PATCH 010/102] fix(package.json): add repo, licenses and switch to devDependencies Fixes #544. Closes #545 --- package.json | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 37a159164..a4eeccea8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,12 @@ { "name": "angular.dart", - "dependencies" : { + "branchVersion": "0.9.*", + "cdnVersion": "0.9.7", + "repository": { + "type": "git", + "url": "https://github.com/angular/angular.dart.git" + }, + "devDependencies": { "karma" : "0.10.2", "karma-dart" : "0.2.6", "karma-script-launcher": "*", @@ -9,6 +15,12 @@ "karma-junit-reporter": "0.1.0", "jasmine-node": "*", "qq": "*" - } + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/angular/angular.dart/blob/master/LICENSE" + } + ], + "dependencies": {} } - From ce6e58fe6d187dc60a829762d87897e3eefa85c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 11 Feb 2014 17:17:53 -0500 Subject: [PATCH 011/102] chore(compiler): remove unused, expired code --- lib/core_dom/compiler.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/core_dom/compiler.dart b/lib/core_dom/compiler.dart index 081bbfa92..45add788e 100644 --- a/lib/core_dom/compiler.dart +++ b/lib/core_dom/compiler.dart @@ -176,10 +176,9 @@ class Compiler { mappingFn = (NodeAttrs attrs, Scope scope, Object dst) { if (attrs[attrName] == null) return; Expression attrExprFn = _parser(attrs[attrName]); - var shadowValue = null; scope.$watch( () => attrExprFn.eval(scope), - (v) => dstPathFn.assign(dst, shadowValue = v), + (v) => dstPathFn.assign(dst, v), attrs[attrName]); }; break; From 89f06bb75a2abd1e268670dcb2eabcef3e302b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Tue, 11 Feb 2014 17:18:18 -0500 Subject: [PATCH 012/102] refactor(forms): remove _NgModelValidator in favor of using a shared interface _NgModelValidator acted as the base class for validators in AngularDart with earlier releases. This works, but it poses a problem for custom validators that exist outside of the AngularDart core. This is because Inheritance is a problem since it is highly limited when it comes to further refactoring and customization. Interfaces are more versatile. Therefore all core and custom validators must implement the shared interface so that they can be attached to ngModel for validation purposes. --- lib/directive/ng_model.dart | 12 +- lib/directive/ng_model_validators.dart | 191 ++++++------------- test/directive/ng_model_validators_spec.dart | 4 +- 3 files changed, 71 insertions(+), 136 deletions(-) diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index 9e9d4c5c0..892ae973f 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -16,7 +16,7 @@ class NgModel extends NgControl { BoundSetter setter = (_, [__]) => null; String _exp; - final List<_NgModelValidator> _validators = new List<_NgModelValidator>(); + final _validators = []; bool _watchCollection; Function _removeWatch = () => null; @@ -69,21 +69,21 @@ class NgModel extends NgControl { validate() { if (validators.isNotEmpty) { validators.forEach((validator) { - setValidity(validator.name, validator.isValid()); + setValidity(validator.name, validator.isValid(viewValue)); }); } else { valid = true; } } - setValidity(String name, bool isValid) { - this.updateControlValidity(this, name, isValid); + setValidity(String name, bool valid) { + this.updateControlValidity(this, name, valid); } /** * Registers a validator into the model to consider when running validate(). */ - addValidator(_NgModelValidator v) { + addValidator(NgValidatable v) { validators.add(v); validate(); } @@ -91,7 +91,7 @@ class NgModel extends NgControl { /** * De-registers a validator from the model. */ - removeValidator(_NgModelValidator v) { + removeValidator(NgValidatable v) { validators.remove(v); validate(); } diff --git a/lib/directive/ng_model_validators.dart b/lib/directive/ng_model_validators.dart index dde1d7996..7fd5ba5db 100644 --- a/lib/directive/ng_model_validators.dart +++ b/lib/directive/ng_model_validators.dart @@ -1,67 +1,30 @@ part of angular.directive; -/** - * _NgModelValidator refers to the required super-class which is used when creating - * validation services that are used with [ngModel]. It is expected that any child-classes - * that inherit from this perform the necessary logic to return a simple true/false response - * when validating the contents of the model data. - */ -abstract class _NgModelValidator { - final dom.Element inputElement; - final NgModel ngModel; - final Scope scope; - bool _listening = false; - - _NgModelValidator(this.inputElement, this.ngModel, this.scope); - - /** - * Registers the validator with to attached model. - */ - void listen() { - if (!_listening) { - _listening = true; - this.ngModel.addValidator(this); - } - } - - get value => ngModel.viewValue; - - /** - * De-registers the validator with to attached model. - */ - void unlisten() { - if (_listening) { - _listening = false; - this.ngModel.removeValidator(this); - } - } - - /** - * Returns true/false depending on the status of the validator's validation mechanism - */ - bool isValid(); +abstract class NgValidatable { + String get name; + bool isValid(value); } /** * Validates the model depending if required or ng-required is present on the element. */ -@NgDirective(selector: '[ng-model][required]') +@NgDirective( + selector: '[ng-model][required]') @NgDirective( selector: '[ng-model][ng-required]', map: const {'ng-required': '=>required'}) -class NgModelRequiredValidator extends _NgModelValidator { - bool _required; - get name => 'required'; - - NgModelRequiredValidator(dom.Element inputElement, NgModel ngModel, - Scope scope, NodeAttrs attrs): - super(inputElement, ngModel, scope) { - if (attrs['required'] != null) required = true; - } +class NgModelRequiredValidator implements NgValidatable { + bool _required = true; + + String get name => 'required'; + + NgModelRequiredValidator(NgModel ngModel) { + ngModel.addValidator(this); + } - bool isValid() { + bool isValid(value) { // Any element which isn't required is always valid. - if (!required) return true; + if (!_required) return true; // Null is not a value, therefore not valid. if (value == null) return false; // Empty lists and/or strings are not valid. @@ -70,15 +33,8 @@ class NgModelRequiredValidator extends _NgModelValidator { return !((value is List || value is String) && value.isEmpty); } - @NgAttr('required') - get required => _required; set required(value) { - if (value is String) return; - if ((_required = value) == true) { - listen(); - } else { - unlisten(); - } + _required = value == null ? false : value; } } @@ -86,19 +42,18 @@ class NgModelRequiredValidator extends _NgModelValidator { * Validates the model to see if its contents match a valid URL pattern. */ @NgDirective(selector: 'input[type=url][ng-model]') -class NgModelUrlValidator extends _NgModelValidator { +class NgModelUrlValidator implements NgValidatable { static final URL_REGEXP = new RegExp( r'^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?' + r'(\/|\/([\w#!:.?+=&%@!\-\/]))?$'); - get name => 'url'; + String get name => 'url'; - NgModelUrlValidator(dom.Element inputElement, NgModel ngModel, Scope scope): - super(inputElement, ngModel, scope) { - listen(); - } + NgModelUrlValidator(NgModel ngModel) { + ngModel.addValidator(this); + } - bool isValid() => + bool isValid(value) => value == null || value.isEmpty || URL_REGEXP.hasMatch(value); } @@ -106,18 +61,17 @@ class NgModelUrlValidator extends _NgModelValidator { * Validates the model to see if its contents match a valid email pattern. */ @NgDirective(selector: 'input[type=email][ng-model]') -class NgModelEmailValidator extends _NgModelValidator { +class NgModelEmailValidator implements NgValidatable { static final EMAIL_REGEXP = new RegExp( r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$'); - get name => 'email'; + String get name => 'email'; - NgModelEmailValidator(dom.Element inputElement, NgModel ngModel, Scope scope): - super(inputElement, ngModel, scope) { - listen(); - } + NgModelEmailValidator(NgModel ngModel) { + ngModel.addValidator(this); + } - bool isValid() => + bool isValid(value) => value == null || value.isEmpty || EMAIL_REGEXP.hasMatch(value); } @@ -125,15 +79,14 @@ class NgModelEmailValidator extends _NgModelValidator { * Validates the model to see if its contents match a valid number. */ @NgDirective(selector: 'input[type=number][ng-model]') -class NgModelNumberValidator extends _NgModelValidator { - get name => 'number'; +class NgModelNumberValidator implements NgValidatable { + String get name => 'number'; - NgModelNumberValidator(dom.Element inputElement, NgModel ngModel, Scope scope): - super(inputElement, ngModel, scope) { - listen(); - } + NgModelNumberValidator(NgModel ngModel) { + ngModel.addValidator(this); + } - bool isValid() { + bool isValid(value) { if (value != null) { try { num val = double.parse(value.toString()); @@ -153,36 +106,24 @@ class NgModelNumberValidator extends _NgModelValidator { @NgDirective( selector: '[ng-model][ng-pattern]', map: const {'ng-pattern': '=>pattern'}) -class NgModelPatternValidator extends _NgModelValidator { +class NgModelPatternValidator implements NgValidatable { RegExp _pattern; - get name => 'pattern'; - - NgModelPatternValidator(dom.Element inputElement, NgModel ngModel, Scope scope): - super(inputElement, ngModel, scope) { - listen(); - } + String get name => 'pattern'; - bool isValid() { - if (_pattern != null && value != null && value.length > 0) { - return _pattern.hasMatch(ngModel.viewValue); - } + NgModelPatternValidator(NgModel ngModel) { + ngModel.addValidator(this); + } + bool isValid(value) { //remember, only required validates for the input being empty - return true; + return _pattern == null || value == null || value.length == 0 || + _pattern.hasMatch(value); } @NgAttr('pattern') - get pattern => _pattern; - set pattern(val) { - if (val != null && val.length > 0) { - _pattern = new RegExp(val); - listen(); - } else { - _pattern = null; - unlisten(); - } - } + set pattern(val) => + _pattern = val != null && val.length > 0 ? new RegExp(val) : null; } /** @@ -194,29 +135,24 @@ class NgModelPatternValidator extends _NgModelValidator { @NgDirective( selector: '[ng-model][ng-minlength]', map: const {'ng-minlength': '=>minlength'}) -class NgModelMinLengthValidator extends _NgModelValidator { +class NgModelMinLengthValidator implements NgValidatable { int _minlength; - get name => 'minlength'; + String get name => 'minlength'; - NgModelMinLengthValidator(dom.Element inputElement, NgModel ngModel, - Scope scope) : super(inputElement, ngModel, scope) { - listen(); - } + NgModelMinLengthValidator(NgModel ngModel) { + ngModel.addValidator(this); + } - bool isValid() { + bool isValid(value) { //remember, only required validates for the input being empty - if (_minlength == 0 || value == null || value.length == 0) { - return true; - } - return value.length >= _minlength; + return _minlength == 0 || value == null || value.length == 0 || + value.length >= _minlength; } @NgAttr('minlength') - get minlength => _minlength; - set minlength(value) { - _minlength = value == null ? 0 : int.parse(value.toString()); - } + set minlength(value) => + _minlength = value == null ? 0 : int.parse(value.toString()); } /** @@ -228,22 +164,19 @@ class NgModelMinLengthValidator extends _NgModelValidator { @NgDirective( selector: '[ng-model][ng-maxlength]', map: const {'ng-maxlength': '=>maxlength'}) -class NgModelMaxLengthValidator extends _NgModelValidator { +class NgModelMaxLengthValidator implements NgValidatable { int _maxlength = 0; - get name => 'maxlength'; + String get name => 'maxlength'; - NgModelMaxLengthValidator(dom.Element inputElement, NgModel ngModel, - Scope scope): super(inputElement, ngModel, scope) { - listen(); - } + NgModelMaxLengthValidator(NgModel ngModel) { + ngModel.addValidator(this); + } - bool isValid() => + bool isValid(value) => _maxlength == 0 || (value == null ? 0 : value.length) <= _maxlength; @NgAttr('maxlength') - get maxlength => _maxlength; - set maxlength(value) { - _maxlength = value == null ? 0 : int.parse(value.toString()); - } + set maxlength(value) => + _maxlength = value == null ? 0 : int.parse(value.toString()); } diff --git a/test/directive/ng_model_validators_spec.dart b/test/directive/ng_model_validators_spec.dart index 1198e4583..3709400fd 100644 --- a/test/directive/ng_model_validators_spec.dart +++ b/test/directive/ng_model_validators_spec.dart @@ -10,7 +10,7 @@ describe('ngModel validators', () { describe('required', () { it('should validate the input field if the required attribute is set', inject((Scope scope) { - _.compile(''); + _.compile(''); Probe probe = _.rootScope.i; var model = probe.directive(NgModel); @@ -48,6 +48,8 @@ describe('ngModel validators', () { Probe probe = _.rootScope.i; var model = probe.directive(NgModel); + _.rootScope.$apply(); + model.validate(); expect(model.valid).toEqual(true); expect(model.invalid).toEqual(false); From 16906a43e50fff16f969ac58aa18f25f9495c6d6 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Tue, 11 Feb 2014 21:35:33 -0600 Subject: [PATCH 013/102] chore(documentation-generation): prepare for transition from dartdoc to docgen Closes #553 --- scripts/env.sh | 2 +- scripts/generate-documentation.sh | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/env.sh b/scripts/env.sh index 8ba3b593d..dd01ad9be 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -29,7 +29,7 @@ export DART=${DART:-"$DARTSDK/bin/dart"} export PUB=${PUB:-"$DARTSDK/bin/pub"} export DARTANALYZER=${DARTANALYZER:-"$DARTSDK/bin/dartanalyzer"} export DARTDOC=${DARTDOC:-"$DARTSDK/bin/dartdoc"} - +export DART_DOCGEN=${DART_DOCGEN:-"$DARTSDK/bin/docgen"} export CHROME_CANARY_BIN=${CHROME_CANARY_BIN:-"$DARTIUM"} export CHROME_BIN=${CHROME_BIN:-"google-chrome"} diff --git a/scripts/generate-documentation.sh b/scripts/generate-documentation.sh index fbfb3153c..c6bf8bc48 100755 --- a/scripts/generate-documentation.sh +++ b/scripts/generate-documentation.sh @@ -1,8 +1,28 @@ #!/bin/bash . $(dirname $0)/env.sh -$DARTDOC \ + +# Temporary during transition period from use of dartdoc to docgen. +if [ -x "$DART_DOCGEN" ]; then + # docgen seems to freeze when it processes the angular.dart files + # https://code.google.com/p/dart/issues/detail?id=16752 + # so disable it for now + # DOC_CMD="$DART_DOCGEN" + # MODE_OPTION= + echo "DISABLING DOCUMENT GENERATION due to isses with docgen." + echo "https://code.google.com/p/dart/issues/detail?id=16752" + echo "----" + echo "Reporting success none-the-less during this docgen beta period." + exit 0; +elif [ -x "$DARTDOC" ]; then + DOC_CMD="$DARTDOC" + MODE_OPTION="--mode=static" +fi + +echo "Generating documentation using $DOC_CMD" +"$DOC_CMD" $MODE_OPTION \ --package-root=packages/ \ --out doc \ - --mode=static \ --exclude-lib=js,metadata,meta,mirrors,intl,number_symbols,number_symbol_data,intl_helpers,date_format_internal,date_symbols,angular.util \ - packages/angular/angular.dart lib/mock/module.dart \ + packages/angular/angular.dart lib/mock/module.dart + + From b59f744ba7c957433562947909cd0a8dfcd2b1cb Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Tue, 11 Feb 2014 21:44:46 -0800 Subject: [PATCH 014/102] chore(test): remove accidental ddescribe --- test/directive/ng_form_spec.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index 1a082f713..f48ee64f2 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -3,7 +3,7 @@ library form_spec; import '../_specs.dart'; main() => -ddescribe('form', () { +describe('form', () { TestBed _; it('should set the name of the form and attach it to the scope', inject((Scope scope, TestBed _) { From e2a00abe371bb2d9d3c1d3c19849e075a32e92e4 Mon Sep 17 00:00:00 2001 From: Chirayu Krishnappa Date: Wed, 12 Feb 2014 00:00:37 -0800 Subject: [PATCH 015/102] fix(generator): remove invalid sort on elements cacheUris are sorted later. sorting the elements can be invalid (e.g. SimpleStringLiteral has == but no compareTo) Closes #554 --- lib/tools/template_cache_generator.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/tools/template_cache_generator.dart b/lib/tools/template_cache_generator.dart index 3578a5b9a..3c896b8c3 100644 --- a/lib/tools/template_cache_generator.dart +++ b/lib/tools/template_cache_generator.dart @@ -175,7 +175,6 @@ class TemplateCollectingVisitor { var paramName = namedArg.name.label.name; if (paramName == 'preCacheUrls') { assertList(namedArg.expression).elements - ..sort() ..forEach((expression) => cacheUris.add(assertString(expression).stringValue)); } From eaaa337b126eada3ddb320989fdff0395295931e Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Tue, 11 Feb 2014 14:42:29 -0600 Subject: [PATCH 016/102] docs(contributing): add developer docs explaining how to setup and run tests - Create a first version of developer documentation explaining how to setup and run AngularDart tests locally and on Travis. - Update `CONTRIBUTING.md` to refer to this new doc. Closes #322. Assumes #545. Closes #548 --- CONTRIBUTING.md | 7 +-- DEVELOPER.md | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 5 deletions(-) create mode 100644 DEVELOPER.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8a90dde5..3d12f293a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,11 +90,7 @@ Before you submit your pull request consider the following guidelines: git commit -a ``` -* Build your changes locally to ensure all the tests pass - - ```shell - ./run-test.sh - ``` +* Build your changes locally to ensure all the tests pass: see the [developer documentation][dev-doc]. * Push your branch to Github: @@ -226,3 +222,4 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise [commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit# [communityMilestone]: https://github.com/angular/angular.dart/issues?milestone=13&state=open [coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md +[dev-doc]: https://github.com/angular/angular.dart/blob/master/DEVELOPER.md diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 000000000..d77236b93 --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,141 @@ +# Building and Testing AngularDart + +This document describes how to set up your development environment to build and test AngularDart, and +explains the basic mechanics of using `git`, `node`, and `npm`. + +See the [contributing guidelines](https://github.com/angular/angular.dart/blob/master/CONTRIBUTING.md) for how to contribute your own code to + +1. [Prerequisite Software](#prerequisite-software) +2. [Getting the Sources](#getting-the-sources) +3. [Environment Variable Setup](#environment-variable-setup) +4. [Installing NPM Modules and Dart Packages](#installing-npm-modules-and-dart-packages) +5. [Running Tests Locally](#running-tests-locally) +6. [Continuous Integration using Travis](#continuous-integration-using-travis) + +## Prerequisite Software + +Before you can build and test AngularDart, you must install and configure the +following products on your development machine: + +* [Dart](https://www.dartlang.org/): as can be expected, AngularDart requires + an installation of the Dart-SDK and Dartium (a version of + [Chromium](http://www.chromium.org) with native support for Dart through the + Dart VM). One of the **simplest** ways to get both is to install the **Dart + Editor bundle**, which includes the editor, sdk and Dartium. See the [Dart + tools download page for + instructions](https://www.dartlang.org/tools/download.html). + +* [Git](http://git-scm.com/) and/or the **Github app** (for + [Mac](http://mac.github.com/) or [Windows](http://windows.github.com/)): the + [Github Guide to Installing + Git](https://help.github.com/articles/set-up-git) is a good source of + information. + +* [Node.js](http://nodejs.org): We use Node to run a development web server, + run tests, and generate distributable files. We also use Node's Package + Manager (`npm`). Depending on your system, you can install Node either from + source or as a pre-packaged bundle. + +## Getting the Sources + +Forking and Cloning the AngularDart repository: + +1. Login to your Github account or create one by following the instructions given [here](https://github.com/signup/free). +Afterwards. +2. [Fork](http://help.github.com/forking) the [main AngularDart repository](https://github.com/angular/angular.dart). +3. Clone your fork of the AngularDart repository and define an `upstream` remote pointing back to the AngularDart repository that you forked in the first place: + +```shell +# Clone your Github repository: +git clone git@github.com:/angular.dart.git + +# Go to the AngularDart directory: +cd angular.dart + +# Add the main AngularDart repository as an upstream remote to your repository: +git remote add upstream https://github.com/angular/angular.dart.git +``` + +## Environment Variable Setup + + +Define the environment variables listed below. These are mainly needed for the +test scripts. The notation shown here is for +[`bash`](http://www.gnu.org/software/bash/); adapt as appropriate for your +favorite shell. (Examples given below of possible values for initializing the +environment variables assume Mac OS X and that you have installed the Dart +Editor in the directory named by `$DART_EDITOR_DIR`. This is only for +illustrative purposes.) + +```shell +# CHROME_BIN: path to a Chrome browser executable; e.g., +export CHROME_BIN="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + +# CHROME_CANARY_BIN: path to a Dartium browser executable; e.g., +export CHROME_CANARY_BIN="$DART_EDITOR_DIR/chromium/Chromium.app/Contents/MacOS/Chromium" +``` + +You should also add the Dart SDK `bin` directory to your path and/or define `DART_SDK`; e.g. + +```shell +# DART_SDK: path to a Dart SDK directory; e.g., +export DART_SDK="$DART_EDITOR_DIR/dart-sdk" + +# Update PATH to include the Dart SDK bin directory +PATH+=":$DART_SDK/bin" +``` +## Installing NPM Modules and Dart Packages + +Next, install the modules and packages needed to run AngularDart tests: + +```shell +# Install node.js dependencies: +npm install + +# Install Dart packages +pub install +``` + +## Running Tests Locally + +NOTE: scripts are being written to embody the following steps. + +To run base tests: + +```shell +# Source a script to define yet more environment variables +. ./scripts/env.sh + +# Run io tests: +$DART --checked test/io/all.dart + +# Run expression extractor tests: +scripts/test-expression-extractor.sh + +Run the Dart Analyzer: +./scripts/analyze.sh +``` + +To run Karma tests over Dartium, execute the following shell commands (which +will launch the Karma server): + +```shell +. ./scripts/env.sh +node "node_modules/karma/bin/karma" start karma.conf \ + --reporters=junit,dots --port=8765 --runner-port=8766 \ + --browsers=Dartium +``` + +In another shell window or tab, or from your favorite IDE, launch the Karma +tests proper by executing: + +```shell +. ./scripts/env.sh +karma_run.sh +``` + +## Continuous Integration using Travis + +See the instructions given [here](https://github.com/angular/angular.dart/blob/master/travis.md). + +----- From c75202d5d7ecabd01366f2198e0c0c3b5c087e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 12 Feb 2014 11:32:06 -0500 Subject: [PATCH 017/102] feat(forms): provide support for reseting forms, fieldsets and models --- lib/directive/ng_control.dart | 5 +++++ lib/directive/ng_model.dart | 10 ++++++++++ test/directive/ng_form_spec.dart | 31 +++++++++++++++++++++++++++++++ test/directive/ng_model_spec.dart | 26 ++++++++++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart index a9042c75d..1d787b5f9 100644 --- a/lib/directive/ng_control.dart +++ b/lib/directive/ng_control.dart @@ -32,6 +32,10 @@ abstract class NgControl implements NgDetachAware { } } + reset() { + _scope.$broadcast('resetNgModel'); + } + get name => _name; set name(value) { _name = value; @@ -168,5 +172,6 @@ class NgNullControl implements NgControl { get invalid => null; set invalid(value) {} + reset() => null; detach() => null; } diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index 892ae973f..b6a7b67b7 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -15,6 +15,7 @@ class NgModel extends NgControl { BoundGetter getter = ([_]) => null; BoundSetter setter = (_, [__]) => null; + var _lastValue; String _exp; final _validators = []; @@ -26,6 +27,11 @@ class NgModel extends NgControl { super(scope, element, injector) { _exp = 'ng-model=${attrs["ng-model"]}'; watchCollection = false; + scope.$on('resetNgModel', reset); + } + + reset() { + modelValue = _lastValue; } @NgAttr('name') @@ -51,6 +57,10 @@ class NgModel extends NgControl { set model(BoundExpression boundExpression) { getter = boundExpression; setter = boundExpression.assign; + + _scope.$evalAsync((value) { + _lastValue = modelValue; + }); } // TODO(misko): right now viewValue and modelValue are the same, diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index f48ee64f2..6b3da4ab4 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -306,6 +306,37 @@ describe('form', () { })); }); + describe('reset()', () { + it('should reset the model value to its original state', inject((TestBed _) { + _.compile('
' + + ' ' + + '
'); + _.rootScope.$apply('myModel = "animal"'); + + NgForm form = _.rootScope['superForm']; + + Probe probe = _.rootScope.i; + var model = probe.directive(NgModel); + + expect(_.rootScope.myModel).toEqual('animal'); + expect(model.modelValue).toEqual('animal'); + expect(model.viewValue).toEqual('animal'); + + _.rootScope.$apply('myModel = "man"'); + + expect(_.rootScope.myModel).toEqual('man'); + expect(model.modelValue).toEqual('man'); + expect(model.viewValue).toEqual('man'); + + form.reset(); + _.rootScope.$apply(); + + expect(_.rootScope.myModel).toEqual('animal'); + expect(model.modelValue).toEqual('animal'); + expect(model.viewValue).toEqual('animal'); + })); + }); + describe('regression tests: form', () { it('should be resolvable by injector if configured by user.', () { module((Module module) { diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index 8445713a2..2c9fef456 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -772,6 +772,32 @@ describe('ng-model', () { }); }); + + describe('reset()', () { + it('should reset the model value to its original state', () { + _.compile(''); + _.rootScope.$apply('myModel = "animal"'); + + Probe probe = _.rootScope.i; + var model = probe.directive(NgModel); + + expect(_.rootScope.myModel).toEqual('animal'); + expect(model.modelValue).toEqual('animal'); + expect(model.viewValue).toEqual('animal'); + + _.rootScope.$apply('myModel = "man"'); + + expect(_.rootScope.myModel).toEqual('man'); + expect(model.modelValue).toEqual('man'); + expect(model.viewValue).toEqual('man'); + + model.reset(); + + expect(_.rootScope.myModel).toEqual('animal'); + expect(model.modelValue).toEqual('animal'); + expect(model.viewValue).toEqual('animal'); + }); + }); }); @NgController( From 52185395965e21c5388d8edbb7e1055d78e36a1b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 12 Feb 2014 13:59:19 -0800 Subject: [PATCH 018/102] chore(style): clean up indentation. --- test/directive/input_select_spec.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/directive/input_select_spec.dart b/test/directive/input_select_spec.dart index b69a91eef..9947b25a6 100644 --- a/test/directive/input_select_spec.dart +++ b/test/directive/input_select_spec.dart @@ -14,9 +14,9 @@ main() { it('should retrieve using ng-value', () { _.compile( ''); - var r2d2 = { "name":"r2d2"}; + var r2d2 = {"name":"r2d2"}; var c3p0 = {"name":"c3p0"}; _.rootScope.robots = [ r2d2, c3p0 ]; _.rootScope.$digest(); From 8fc2c0f49aabc53ee6240ad8063ecf6c9c8b8a1f Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 12 Feb 2014 20:09:57 -0800 Subject: [PATCH 019/102] fix(ng-value): Add ng-value support for checked/radio/option Closes# 543 --- lib/directive/input_select.dart | 20 +++---- lib/directive/module.dart | 3 + lib/directive/ng_model.dart | 91 +++++++++++++++++++++++++++---- test/directive/ng_model_spec.dart | 77 ++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 23 deletions(-) diff --git a/lib/directive/input_select.dart b/lib/directive/input_select.dart index 4127fec40..fffbb52de 100644 --- a/lib/directive/input_select.dart +++ b/lib/directive/input_select.dart @@ -86,24 +86,23 @@ class InputSelectDirective implements NgAttachAware { */ @NgDirective( selector: 'option', - publishTypes: const [TextChangeListener], - map: const {'ng-value': '&ngValue'}) + publishTypes: const [TextChangeListener]) class OptionValueDirective implements TextChangeListener, NgAttachAware, NgDetachAware { final InputSelectDirective _inputSelectDirective; - final NodeAttrs _attrs; + final dom.Element _element; - BoundGetter _ngValue; + NgValue _ngValue; - OptionValueDirective(this._attrs, this._inputSelectDirective) { + OptionValueDirective(this._element, this._inputSelectDirective, this._ngValue) { if (_inputSelectDirective != null) { - _inputSelectDirective.expando[_attrs.element] = this; + _inputSelectDirective.expando[_element] = this; } } attach() { if (_inputSelectDirective != null) { - this._attrs.observe('value', (_) => _inputSelectDirective.dirty()); + _inputSelectDirective.dirty(); } } @@ -116,14 +115,11 @@ class OptionValueDirective implements TextChangeListener, NgAttachAware, detach() { if (_inputSelectDirective != null) { _inputSelectDirective.dirty(); - _inputSelectDirective.expando[_attrs.element] = null; + _inputSelectDirective.expando[_element] = null; } } - set ngValue(BoundGetter value) => _ngValue = value; - get ngValue => _attrs['ng-value'] is String ? - _ngValue() : - (_attrs.element as dom.OptionElement).value; + get ngValue => _ngValue.readValue(_element); } class _SelectMode { diff --git a/lib/directive/module.dart b/lib/directive/module.dart index 102e3406a..a866caa21 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -59,6 +59,9 @@ class NgDirectiveModule extends Module { value(OptionValueDirective, null); value(ContentEditableDirective, null); value(NgModel, null); + value(NgValue, new NgValue(null)); + value(NgTrueValue, new NgTrueValue(null)); + value(NgFalseValue, new NgFalseValue(null)); value(NgSwitchDirective, null); value(NgSwitchWhenDirective, null); value(NgSwitchDefaultDirective, null); diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index b6a7b67b7..1b6797ab6 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -11,7 +11,7 @@ part of angular.directive; * (to be implemented) */ @NgDirective(selector: '[ng-model]') -class NgModel extends NgControl { +class NgModel extends NgControl implements NgAttachAware { BoundGetter getter = ([_]) => null; BoundSetter setter = (_, [__]) => null; @@ -26,8 +26,12 @@ class NgModel extends NgControl { NgModel(Scope scope, NodeAttrs attrs, dom.Element element, Injector injector) : super(scope, element, injector) { _exp = 'ng-model=${attrs["ng-model"]}'; + } + + + attach() { watchCollection = false; - scope.$on('resetNgModel', reset); + _scope.$on('resetNgModel', reset); } reset() { @@ -117,22 +121,26 @@ class NgModel extends NgControl { * is falsy (i.e. one of `false`, `null`, and `0`), then the checkbox is * unchecked. Otherwise, it is checked.  Likewise, when the checkbox is checked, * the model value is set to true. When unchecked, it is set to false. - * - * The AngularJS style ng-true-value / ng-false-value is not supported. */ @NgDirective(selector: 'input[type=checkbox][ng-model]') class InputCheckboxDirective { final dom.InputElement inputElement; final NgModel ngModel; + final NgTrueValue ngTrueValue; + final NgFalseValue ngFalseValue; final Scope scope; InputCheckboxDirective(dom.Element this.inputElement, this.ngModel, - this.scope) { + this.scope, this.ngTrueValue, this.ngFalseValue) { ngModel.render = (value) { - inputElement.checked = value == null ? false : toBool(value); + inputElement.checked = ngTrueValue.isValue(inputElement, value); }; inputElement.onChange.listen((value) { - scope.$apply(() => ngModel.viewValue = inputElement.checked); + scope.$apply(() { + ngModel.viewValue = inputElement.checked + ? ngTrueValue.readValue(inputElement) + : ngFalseValue.readValue(inputElement); + }); }); } } @@ -259,6 +267,66 @@ class _UidCounter { final _uidCounter = new _UidCounter(); +/** + * Use `ng-value` directive with `` or `