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);