From ec8b4e7d897288d919ab5cdab5278ea6dc30239b Mon Sep 17 00:00:00 2001 From: Marko Vuksanovic Date: Tue, 6 May 2014 00:06:51 +1000 Subject: [PATCH] feat(NodeAttrs): Node attributes don't get initialized by NodeAttrs when setting up observing. In case an element does not have a value set for a propertyv, that property is not automatically initialised. For example:
will cause ElementBinder to register observer (which will in turn cause initialisation) on property foo (of the directive) to bar, but if we try using
foo property will not be initialised. --- lib/core_dom/directive.dart | 5 +++- lib/core_dom/element_binder.dart | 11 +++++--- lib/directive/ng_model.dart | 2 +- lib/directive/ng_model_select.dart | 26 ++++++++++++------- lib/directive/ng_model_validators.dart | 1 + test/core_dom/directive_spec.dart | 35 +++++++++++++++++++++++--- test/directive/ng_form_spec.dart | 2 ++ 7 files changed, 63 insertions(+), 19 deletions(-) diff --git a/lib/core_dom/directive.dart b/lib/core_dom/directive.dart index ec846f7ca..125db2e66 100644 --- a/lib/core_dom/directive.dart +++ b/lib/core_dom/directive.dart @@ -57,7 +57,10 @@ class NodeAttrs { if (_mustacheAttrs[attrName].isComputed) notifyFn(this[attrName]); _mustacheAttrs[attrName].notifyFn(true); } else { - notifyFn(this[attrName]); + if (element.attributes.containsKey(attrName)) { + var value = element.attributes[attrName]; + notifyFn(this[attrName]); + } } } diff --git a/lib/core_dom/element_binder.dart b/lib/core_dom/element_binder.dart index 79be68c1c..56f019667 100644 --- a/lib/core_dom/element_binder.dart +++ b/lib/core_dom/element_binder.dart @@ -124,10 +124,9 @@ class ElementBinder { dstPathFn.assign(controller, _parser(expression).bind(scope.context, ScopeLocals.wrapper)); } - - void _createAttrMappings(directive, scope, List mappings, nodeAttrs, formatters, + void _createAttrMappings(directive, scope, DirectiveRef ref, nodeAttrs, formatters, tasks) { - mappings.forEach((MappingParts p) { + ref.mappings.forEach((MappingParts p) { var attrName = p.attrName; var dstExpression = p.dstExpression; @@ -154,6 +153,10 @@ class ElementBinder { switch (p.mode) { case '@': // string var taskId = tasks.registerTask(); + if (ref.element is dom.Element && + !(ref.element as dom.Element).attributes.containsKey(attrName)) { + tasks.completeTask(taskId); + } nodeAttrs.observe(attrName, (value) { dstPathFn.assign(directive, value); tasks.completeTask(taskId); @@ -221,7 +224,7 @@ class ElementBinder { if (ref.mappings.isNotEmpty) { if (nodeAttrs == null) nodeAttrs = new _AnchorAttrs(ref); - _createAttrMappings(directive, scope, ref.mappings, nodeAttrs, formatters, tasks); + _createAttrMappings(directive, scope, ref, nodeAttrs, formatters, tasks); } if (directive is AttachAware) { diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index 4571ee8f3..d670fb09b 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -67,6 +67,7 @@ class NgModel extends NgControl implements AttachAware { void attach() { watchCollection = false; + _parentControl.addControl(this); } /** @@ -127,7 +128,6 @@ class NgModel extends NgControl implements AttachAware { String get name => _name; void set name(value) { _name = value; - _parentControl.addControl(this); } // TODO(misko): could we get rid of watch collection, and just always watch the collection? diff --git a/lib/directive/ng_model_select.dart b/lib/directive/ng_model_select.dart index 1e8e6e047..5e84135df 100644 --- a/lib/directive/ng_model_select.dart +++ b/lib/directive/ng_model_select.dart @@ -40,17 +40,25 @@ class InputSelect implements AttachAware { } attach() { + var singleSelectMode = () { + _model.watchCollection = false; + _mode = new _SingleSelectMode(expando, _selectElement, _model, _nullOption, _unknownOption); + _mode.onModelChange(_model.viewValue); + }; + + var multiSelectMode = () { + _model.watchCollection = true; + _mode = new _MultipleSelectionMode(expando, _selectElement, _model); + _mode.onModelChange(_model.viewValue); + }; + + if (!_selectElement.attributes.containsKey('multiple')) { + singleSelectMode(); + } _attrs.observe('multiple', (value) { _mode.destroy(); - if (value == null) { - _model.watchCollection = false; - _mode = new _SingleSelectMode(expando, _selectElement, _model, - _nullOption, _unknownOption); - } else { - _model.watchCollection = true; - _mode = new _MultipleSelectionMode(expando, _selectElement, _model); - } - _mode.onModelChange(_model.viewValue); + if (value == null || value == '') multiSelectMode(); + else singleSelectMode(); }); _selectElement.onChange.listen((event) => _mode.onViewChange(event)); diff --git a/lib/directive/ng_model_validators.dart b/lib/directive/ng_model_validators.dart index b0537399f..4d0bd7a15 100644 --- a/lib/directive/ng_model_validators.dart +++ b/lib/directive/ng_model_validators.dart @@ -264,6 +264,7 @@ class NgModelMinLengthValidator implements NgValidator { NgModelMinLengthValidator(NgModel this._ngModel) { _ngModel.addValidator(this); + _minlength = 0; } bool isValid(modelValue) { diff --git a/test/core_dom/directive_spec.dart b/test/core_dom/directive_spec.dart index b80f5f4f6..5152c0717 100644 --- a/test/core_dom/directive_spec.dart +++ b/test/core_dom/directive_spec.dart @@ -5,12 +5,12 @@ import '../_specs.dart'; main() { describe('NodeAttrs', () { var element; - var nodeAttrs; + NodeAttrs nodeAttrs; TestBed _; beforeEach((TestBed tb) { _ = tb; - element = _.compile('
'); + element = _.compile('
'); nodeAttrs = new NodeAttrs(element); }); @@ -27,7 +27,7 @@ main() { it('should provide a forEach function to iterate over attributes', () { Map attrMap = new Map(); nodeAttrs.forEach((k, v) => attrMap[k] = v); - expect(attrMap).toEqual({'foo': 'bar', 'foo-bar': 'baz', 'foo-bar-baz': 'foo'}); + expect(attrMap).toEqual({'foo': 'bar', 'foo-bar': 'baz', 'foo-bar-baz': 'foo', 'cux': ''}); }); it('should provide a contains method', () { @@ -38,7 +38,34 @@ main() { }); it('should return the attribute names', () { - expect(nodeAttrs.keys.toList()..sort()).toEqual(['foo', 'foo-bar', 'foo-bar-baz']); + expect(nodeAttrs.keys.toList()..sort()).toEqual(['cux', 'foo', 'foo-bar', 'foo-bar-baz']); + }); + + it('should not call function with argument set to null when observing a' + ' property', () { + var invoked; + nodeAttrs.observe("a", (arg) { + invoked = true; + }); + expect(invoked).toBeFalsy(); + }); + + it('should call function when argument is set when observing a property', + () { + var seenValue = ''; + nodeAttrs.observe("foo", (arg) { + seenValue = arg; + }); + expect(seenValue).toEqual('bar'); + }); + + it('should call function with argument set to \'\' when observing a boolean attribute', + () { + var seenValue; + nodeAttrs.observe("cux", (arg) { + seenValue = arg; + }); + expect(seenValue).toEqual(''); }); }); } diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index 1821f217a..4e6839275 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -710,6 +710,8 @@ void main() { Probe probe = s.context['i']; var model = probe.directive(NgModel); + _.rootScope.apply(); + expect(s.eval('name')).toEqual('cool'); expect(s.eval('myForm.name')).toEqual('myForm'); expect(s.eval('myForm["name"]')).toBe(model);