Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions lib/core_dom/directive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ class NodeAttrs {

NodeAttrs(this.element);

operator [](String attributeName) =>
element.attributes[attributeName];
operator [](String attributeName) {
var value = element.attributes[attributeName];
if (value == null && attributeName.startsWith('ng-')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be special casing ng- any attribute can have a data- not just ng-

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means users cannot define their own directive using a data- attribute as a selector. Is it a spec that all data-foo attributes are taken as foo attributes in all directive selectors?

value = element.attributes['data-$attributeName'];
}
return value;
}

operator []=(String attributeName, String value) {
if (attributeName.startsWith('ng-')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, ng- should not be special

element.attributes.remove('data-$attributeName');
}
if (value == null) {
element.attributes.remove(attributeName);
} else {
Expand Down
34 changes: 30 additions & 4 deletions lib/core_dom/selector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ part of angular.core.dom;
*/
typedef List<DirectiveRef> DirectiveSelector(dom.Node node);

String _normalizeKey(String key) =>
key.startsWith('data-ng-') ? key.substring('data-'.length) : key;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove ng-


class _AttrValueMap<V> implements Map<String, V> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this? Why do we need to subclass Map?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced _AttrValueMap because I'd like to ensure both an attrValueMap property and an attrValuePartialMap property in _ElementSelector class never hold data- attributes. But now I notice there are only a few places where attrValueMap and attrValuePartialMap are set, so you are right. Introducing new class may be too much and it should be eliminated.

var _map = <String, V>{};

void addAll(Map<String, V> other) { _map.addAll(other); }
bool containsKey(String key) => _map.containsKey(_normalizeKey(key));
bool containsValue(Object value) => _map.containsValue(value);
void clear() { _map.clear(); }
void forEach(Function f) { _map.forEach(f); }
V putIfAbsent(String key, Function ifAbsent) =>
_map.putIfAbsent(_normalizeKey(key), ifAbsent);
V remove(String key) => _map.remove(_normalizeKey(key));

V operator[](String key) => _map[_normalizeKey(key)];
void operator[]=(String key, V value) { _map[_normalizeKey(key)] = value; }

bool get isEmpty => _map.isEmpty;
bool get isNotEmpty => _map.isNotEmpty;
int get length => _map.length;
Iterable<String> get keys => _map.keys;
Iterable<V> get values => _map.values;
}

class _Directive {
final Type type;
final NgAnnotation annotation;
Expand Down Expand Up @@ -76,7 +101,6 @@ class _SelectorPart {
: element;
}


class _ElementSelector {
final String name;

Expand All @@ -86,8 +110,8 @@ class _ElementSelector {
var classMap = <String, List<_Directive>>{};
var classPartialMap = <String, _ElementSelector>{};

var attrValueMap = <String, Map<String, List<_Directive>>>{};
var attrValuePartialMap = <String, Map<String, _ElementSelector>>{};
var attrValueMap = new _AttrValueMap<Map<String, List<_Directive>>>();
var attrValuePartialMap = new _AttrValueMap<Map<String, _ElementSelector>>();

_ElementSelector(this.name);

Expand Down Expand Up @@ -175,7 +199,7 @@ class _ElementSelector {
dom.Node node, String attrName,
String attrValue) {

String matchingKey = _matchingKey(attrValueMap.keys, attrName);
String matchingKey = _matchingKey(attrValueMap.keys, _normalizeKey(attrName));

if (matchingKey != null) {
Map<String, List<_Directive>> valuesMap = attrValueMap[matchingKey];
Expand Down Expand Up @@ -306,6 +330,8 @@ DirectiveSelector directiveSelectorFactory(DirectiveMap directives) {
// 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) {
if (attrName.startsWith('data-ng-'))
attrName = attrName.substring('data-'.length);
directiveRefs.add(new DirectiveRef(
node, type, selectorRegExp.annotation, '$attrName=$value'));
});
Expand Down
1 change: 1 addition & 0 deletions lib/directive/ng_cloak.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ part of angular.directive;
class NgCloakDirective {
NgCloakDirective(dom.Element element) {
element.attributes.remove('ng-cloak');
element.attributes.remove('data-ng-cloak');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have data- test on selector / compiler, not piggy-mack on existing tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #560, you wrote we should not iterate over the element attributes to replace data-foo attributes to foo attributes. So if we have to accept users to use data- attributes, I think we should check about data-attributes in each directives which depends on a specific attribute.

element.classes.remove('ng-cloak');
}
}
8 changes: 7 additions & 1 deletion lib/directive/ng_src_boolean.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,15 @@ class NgAttributeDirective implements NgAttachAware {

void attach() {
String ngAttrPrefix = 'ng-attr-';
String dataNgAttrPrefix = 'data-ng-attr-';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing directives should be blissfully unaware of any changes to the data- prefix. The fact that you have to change this, means that the abstraction is wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as previous

_attrs.forEach((key, value) {
var newKey;
if (key.startsWith(ngAttrPrefix)) {
var newKey = key.substring(ngAttrPrefix.length);
newKey = key.substring(ngAttrPrefix.length);
} else if (key.startsWith(dataNgAttrPrefix)) {
newKey = key.substring(dataNgAttrPrefix.length);
}
if (newKey != null) {
_attrs[newKey] = value;
_attrs.observe(key, (newValue) => _attrs[newKey] = newValue );
}
Expand Down
13 changes: 9 additions & 4 deletions test/core_dom/directive_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,29 @@ main() {

beforeEach(inject((TestBed tb) {
_ = tb;
element = _.compile('<div foo="bar" foo-bar="baz" foo-bar-baz="foo"></div>');
element = _.compile('<div foo="bar" foo-bar="baz" foo-bar-baz="foo" data-ng-qux="qux"></div>');
nodeAttrs = new NodeAttrs(element);
}));

it('should transform names to camel case', () {
it('should return attribute value', () {
expect(nodeAttrs['foo']).toEqual('bar');
expect(nodeAttrs['foo-bar']).toEqual('baz');
expect(nodeAttrs['foo-bar-baz']).toEqual('foo');
});

it('should ignore data prefix', () {
expect(nodeAttrs['data-ng-qux']).toEqual('qux');
expect(nodeAttrs['ng-qux']).toEqual('qux');
});

it('should return null for unexistent attributes', () {
expect(nodeAttrs['baz']).toBeNull();
});

it('should provide a forEach function to iterate over attributes', () {
Map<String, String> 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', 'data-ng-qux': 'qux'});
});

it('should provide a contains method', () {
Expand All @@ -38,7 +43,7 @@ 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(['data-ng-qux', 'foo', 'foo-bar', 'foo-bar-baz']);
});
});
}
16 changes: 15 additions & 1 deletion test/core_dom/selector_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import '../_specs.dart';
@NgDirective(selector: '[two-directives]') class _OneOfTwoDirectives {}
@NgDirective(selector: '[two-directives]') class _TwoOfTwoDirectives {}

@NgDirective(selector:'[ng-directive]') class _NgDirectiveAttr{}

main() {
describe('Selector', () {
Expand Down Expand Up @@ -60,7 +61,8 @@ main() {
..type(_IgnoreChildren)
..type(_TwoDirectives)
..type(_OneOfTwoDirectives)
..type(_TwoOfTwoDirectives);
..type(_TwoOfTwoDirectives)
..type(_NgDirectiveAttr);
}));
beforeEach(inject((DirectiveMap directives) {
selector = directiveSelectorFactory(directives);
Expand Down Expand Up @@ -195,6 +197,18 @@ main() {
{ "selector": '[two-directives]', "value": '', "element": element}
]));
});

it('should match directive on [data-ng-attribute] ignoring data prefix', () {
expect(selector(element = e('<div data-ng-directive=abc></div>')),
toEqualsDirectiveInfos([
{ "selector": '[ng-directive]', "value": 'abc', "element": element,
"name": 'ng-directive' }]));

expect(selector(element = e('<div data-ng-directive></div>')),
toEqualsDirectiveInfos([
{ "selector": '[ng-directive]', "value": '', "element": element,
"name": 'ng-directive' }]));
});
});
}

Expand Down
7 changes: 7 additions & 0 deletions test/directive/ng_a_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,12 @@ main() {
_.triggerEvent(_.rootElement, 'click', 'MouseEvent');
expect(window.location.href.endsWith("#url")).toEqual(true);
}));

it('should bind click listener with data attribute', inject((Scope scope) {
_.compile('<a href="" data-ng-click="abc = true; event = \$event"></a>');
_.triggerEvent(_.rootElement, 'click', 'MouseEvent');
expect(_.rootScope.context['abc']).toEqual(true);
expect(_.rootScope.context['event'] is dom.UIEvent).toEqual(true);
}));
});
}
10 changes: 10 additions & 0 deletions test/directive/ng_bind_html_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ main() {
expect(element.html()).toEqual('<a href="http://www.google.com"><b>Google!</b></a>');
});
}));

it('should sanitize and set innerHtml and sanitize and set html with data attribute',
inject((Scope scope, Injector injector, Compiler compiler, DirectiveMap directives) {
var element = $('<div data-ng-bind-html="htmlVar"></div>');
compiler(element, directives)(injector, element);
scope.context['htmlVar'] = '<a href="http://www.google.com"><b>Google!</b></a>';
scope.apply();
// Sanitization removes the href attribute on the <a> tag.
expect(element.html()).toEqual('<a><b>Google!</b></a>');
}));
});
}

Expand Down
8 changes: 8 additions & 0 deletions test/directive/ng_bind_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,13 @@ main() {
});
expect(element.text).toEqual('1');
}));

it('should set.text with data attribute', inject((Scope scope, Injector injector, Compiler compiler, DirectiveMap directives) {
var element = $('<div data-ng-bind="a"></div>');
compiler(element, directives)(injector, element);
scope.context['a'] = "abc123";
scope.apply();
expect(element.text()).toEqual('abc123');
}));
});
}
15 changes: 15 additions & 0 deletions test/directive/ng_bind_template_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,20 @@ main() {

expect(element.text).toEqual('Good-Bye Heisenberg!');
}));

it('should bind template with data attribute',
inject((Scope scope, Injector injector, Compiler compiler) {
var element = _.compile('<div data-ng-bind-template="{{salutation}} {{name}}!"></div>');
scope.context['salutation'] = 'Hello';
scope.context['name'] = 'Heisenberg';
scope.apply();

expect(element.text).toEqual('Hello Heisenberg!');

scope.context['salutation'] = 'Good-Bye';
scope.apply();

expect(element.text).toEqual('Good-Bye Heisenberg!');
}));
});
}
20 changes: 20 additions & 0 deletions test/directive/ng_class_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,25 @@ main() {
expect(e2.classes.contains('even')).toBeTruthy();
expect(e2.classes.contains('odd')).toBeFalsy();
});

it('should add new and remove old classes dynamically with data attribute', () {
var element = _.compile('<div class="existing" data-ng-class="dynClass"></div>');
_.rootScope.context['dynClass'] = 'A';
_.rootScope.apply();
expect(element.classes.contains('existing')).toBe(true);
expect(element.classes.contains('A')).toBe(true);

_.rootScope.context['dynClass'] = 'B';
_.rootScope.apply();
expect(element.classes.contains('existing')).toBe(true);
expect(element.classes.contains('A')).toBe(false);
expect(element.classes.contains('B')).toBe(true);

_.rootScope.context['dynClass'] = null;
_.rootScope.apply();
expect(element.classes.contains('existing')).toBe(true);
expect(element.classes.contains('A')).toBe(false);
expect(element.classes.contains('B')).toBe(false);
});
});
}
7 changes: 7 additions & 0 deletions test/directive/ng_cloak_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,12 @@ main() {
expect(element.hasClass('ng-cloak')).toBe(false);
expect(element.hasClass('bar')).toBe(true);
});

it('should get removed when an element is compiled with data attribute', () {
var element = $('<div data-ng-cloak></div>');
expect(element.attr('data-ng-cloak')).toEqual('');
_.compile(element);
expect(element.attr('data-ng-cloak')).toBeNull();
});
});
}
13 changes: 13 additions & 0 deletions test/directive/ng_events_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ void addTest(String name, [String eventType='MouseEvent', String eventName]) {
expect(_.rootScope.context['event'] is dom.UIEvent).toEqual(true);
}));
});

describe('data-ng-$name', () {
TestBed _;

beforeEach(inject((TestBed tb) => _ = tb));

it('should evaluate the expression on data-$name', inject(() {
_.compile('<button data-ng-$name="abc = true; event = \$event"></button>');
_.triggerEvent(_.rootElement, eventName, eventType);
expect(_.rootScope.context['abc']).toEqual(true);
expect(_.rootScope.context['event'] is dom.UIEvent).toEqual(true);
}));
});
}

main() {
Expand Down
25 changes: 25 additions & 0 deletions test/directive/ng_if_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,29 @@ main() {
expect(element.find('span').html()).toEqual('');
}
);

they('should add/remove the element with data attribute',
[ '<div><span data-ng-if="isVisible">content</span></div>',
'<div><span data-ng-unless="!isVisible">content</span></div>'],
(html) {
compile(html);
// The span node should NOT exist in the DOM.
expect(element.contents().length).toEqual(1);
expect(element.find('span').html()).toEqual('');

rootScope.apply(() {
rootScope.context['isVisible'] = true;
});

// The span node SHOULD exist in the DOM.
expect(element.contents().length).toEqual(2);
expect(element.find('span').html()).toEqual('content');

rootScope.apply(() {
rootScope.context['isVisible'] = false;
});

expect(element.find('span').html()).toEqual('');
}
);
}
14 changes: 14 additions & 0 deletions test/directive/ng_include_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,19 @@ main() {
expect(element.text).toEqual('I am Vojta');
})));

it('should fetch template from literal url with data attribute', async(inject((Scope scope, TemplateCache cache) {
cache.put('tpl.html', new HttpResponse(200, 'my name is {{name}}'));

var element = _.compile('<div data-ng-include="tpl.html"></div>');

expect(element.innerHtml).toEqual('');

microLeap(); // load the template from cache.
scope.applyInZone(() {
scope.context['name'] = 'Vojta';
});
expect(element.text).toEqual('my name is Vojta');
})));

});
}
10 changes: 10 additions & 0 deletions test/directive/ng_model_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ describe('ng-model', () {
expect(element.selectionStart).toEqual(3);
expect(element.selectionEnd).toEqual(3);
}));

it('should update input value from model with data attribute', inject(() {
_.compile('<input type="text" data-ng-model="model">');
_.rootScope.apply();

expect((_.rootElement as dom.InputElement).value).toEqual('');

_.rootScope.apply('model = "misko"');
expect((_.rootElement as dom.InputElement).value).toEqual('misko');
}));
});

/* This function simulates typing the given text into the input
Expand Down
Loading