Skip to content

Commit aca4604

Browse files
feat(CSSClass): support binding to classList
Closes angular#876
1 parent 48811cd commit aca4604

File tree

5 files changed

+221
-1
lines changed

5 files changed

+221
-1
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {Decorator} from 'angular2/src/core/annotations/annotations';
2+
import {isPresent} from 'angular2/src/facade/lang';
3+
import {DOM} from 'angular2/src/dom/dom_adapter';
4+
import {NgElement} from 'angular2/src/core/dom/element';
5+
6+
@Decorator({
7+
selector: '[class]',
8+
bind: {
9+
'iterableChanges': 'class | keyValDiff'
10+
}
11+
})
12+
export class CSSClass {
13+
_domEl;
14+
constructor(ngEl: NgElement) {
15+
this._domEl = ngEl.domElement;
16+
}
17+
18+
_toggleClass(className, enabled) {
19+
if (enabled) {
20+
DOM.addClass(this._domEl, className);
21+
} else {
22+
DOM.removeClass(this._domEl, className);
23+
}
24+
}
25+
26+
set iterableChanges(changes) {
27+
if (isPresent(changes)) {
28+
changes.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); });
29+
changes.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); });
30+
changes.forEachRemovedItem((record) => {
31+
if (record.previousValue) {
32+
DOM.removeClass(this._domEl, record.key);
33+
}
34+
});
35+
}
36+
}
37+
}

modules/angular2/src/facade/collection.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ class StringMapWrapper {
6969
static void set(Map map, key, value) {
7070
map[key] = value;
7171
}
72+
static void delete(Map m, k) {
73+
m.remove(k);
74+
}
7275
static void forEach(Map m, fn(v, k)) {
7376
m.forEach((k, v) => fn(v, k));
7477
}

modules/angular2/src/facade/collection.es6

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export class StringMapWrapper {
6262
}
6363
return true;
6464
}
65+
static delete(map, key) { delete map[key]; }
6566
static forEach(map, callback) {
6667
for (var prop in map) {
6768
if (map.hasOwnProperty(prop)) {
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import {
2+
AsyncTestCompleter,
3+
beforeEach,
4+
beforeEachBindings,
5+
ddescribe,
6+
xdescribe,
7+
describe,
8+
el,
9+
expect,
10+
iit,
11+
inject,
12+
it,
13+
xit,
14+
} from 'angular2/test_lib';
15+
16+
import {StringMapWrapper} from 'angular2/src/facade/collection';
17+
18+
import {Injector, bind} from 'angular2/di';
19+
20+
import {Compiler} from 'angular2/src/core/compiler/compiler';
21+
import {TemplateResolver} from 'angular2/src/core/compiler/template_resolver';
22+
23+
import {Template} from 'angular2/src/core/annotations/template';
24+
import {Decorator, Component} from 'angular2/src/core/annotations/annotations';
25+
26+
import {MockTemplateResolver} from 'angular2/src/mock/template_resolver_mock';
27+
28+
import {CSSClass} from 'angular2/src/directives/class';
29+
30+
export function main() {
31+
describe('binding to CSS class list', () => {
32+
33+
var view, cd, compiler, component, tplResolver;
34+
35+
beforeEachBindings(() => [
36+
bind(TemplateResolver).toClass(MockTemplateResolver)
37+
]);
38+
39+
beforeEach(inject([Compiler, TemplateResolver], (c, t) => {
40+
compiler = c;
41+
tplResolver = t;
42+
}));
43+
44+
function createView(pv) {
45+
component = new TestComponent();
46+
view = pv.instantiate(null, null);
47+
view.hydrate(new Injector([]), null, null, component, null);
48+
cd = view.changeDetector;
49+
}
50+
51+
function compileWithTemplate(html) {
52+
var template = new Template({
53+
inline: html,
54+
directives: [CSSClass]
55+
});
56+
tplResolver.setTemplate(TestComponent, template);
57+
return compiler.compile(TestComponent);
58+
}
59+
60+
it('should add classes specified in an object literal', inject([AsyncTestCompleter], (async) => {
61+
var template = '<div [class]="{foo: true, bar: false}"></div>';
62+
compileWithTemplate(template).then((pv) => {
63+
createView(pv);
64+
65+
cd.detectChanges();
66+
expect(view.nodes[0].className).toEqual('ng-binding foo');
67+
68+
async.done();
69+
});
70+
}));
71+
72+
it('should add and remove classes based on changes in object literal values', inject([AsyncTestCompleter], (async) => {
73+
var template = '<div [class]="{foo: condition, bar: !condition}"></div>';
74+
compileWithTemplate(template).then((pv) => {
75+
createView(pv);
76+
cd.detectChanges();
77+
expect(view.nodes[0].className).toEqual('ng-binding foo');
78+
79+
component.condition = false;
80+
cd.detectChanges();
81+
expect(view.nodes[0].className).toEqual('ng-binding bar');
82+
83+
async.done();
84+
});
85+
}));
86+
87+
it('should add and remove classes based on changes to the expression object', inject([AsyncTestCompleter], (async) => {
88+
var template = '<div [class]="expr"></div>';
89+
compileWithTemplate(template).then((pv) => {
90+
createView(pv);
91+
cd.detectChanges();
92+
expect(view.nodes[0].className).toEqual('ng-binding foo');
93+
94+
StringMapWrapper.set(component.expr, 'bar', true);
95+
cd.detectChanges();
96+
expect(view.nodes[0].className).toEqual('ng-binding foo bar');
97+
98+
StringMapWrapper.set(component.expr, 'baz', true);
99+
cd.detectChanges();
100+
expect(view.nodes[0].className).toEqual('ng-binding foo bar baz');
101+
102+
StringMapWrapper.delete(component.expr, 'bar');
103+
cd.detectChanges();
104+
expect(view.nodes[0].className).toEqual('ng-binding foo baz');
105+
106+
async.done();
107+
});
108+
}));
109+
110+
it('should retain existing classes when expression evaluates to null', inject([AsyncTestCompleter], (async) => {
111+
var template = '<div [class]="expr"></div>';
112+
compileWithTemplate(template).then((pv) => {
113+
createView(pv);
114+
cd.detectChanges();
115+
expect(view.nodes[0].className).toEqual('ng-binding foo');
116+
117+
component.expr = null;
118+
cd.detectChanges();
119+
expect(view.nodes[0].className).toEqual('ng-binding foo');
120+
121+
component.expr = {'foo': false, 'bar': true};
122+
cd.detectChanges();
123+
expect(view.nodes[0].className).toEqual('ng-binding bar');
124+
125+
async.done();
126+
});
127+
}));
128+
129+
it('should co-operate with the class attribute', inject([AsyncTestCompleter], (async) => {
130+
var template = '<div [class]="expr" class="init foo"></div>';
131+
compileWithTemplate(template).then((pv) => {
132+
createView(pv);
133+
134+
StringMapWrapper.set(component.expr, 'bar', true);
135+
cd.detectChanges();
136+
expect(view.nodes[0].className).toEqual('init foo ng-binding bar');
137+
138+
StringMapWrapper.set(component.expr, 'foo', false);
139+
cd.detectChanges();
140+
expect(view.nodes[0].className).toEqual('init ng-binding bar');
141+
142+
async.done();
143+
});
144+
}));
145+
146+
it('should co-operate with the class attribute and class.name binding', inject([AsyncTestCompleter], (async) => {
147+
var template = '<div class="init foo" [class]="expr" [class.baz]="condition"></div>';
148+
compileWithTemplate(template).then((pv) => {
149+
createView(pv);
150+
cd.detectChanges();
151+
expect(view.nodes[0].className).toEqual('init foo ng-binding baz');
152+
153+
StringMapWrapper.set(component.expr, 'bar', true);
154+
cd.detectChanges();
155+
expect(view.nodes[0].className).toEqual('init foo ng-binding baz bar');
156+
157+
StringMapWrapper.set(component.expr, 'foo', false);
158+
cd.detectChanges();
159+
expect(view.nodes[0].className).toEqual('init ng-binding baz bar');
160+
161+
component.condition = false;
162+
cd.detectChanges();
163+
expect(view.nodes[0].className).toEqual('init ng-binding bar');
164+
165+
async.done();
166+
});
167+
}));
168+
})
169+
}
170+
171+
@Component({selector: 'test-cmp'})
172+
class TestComponent {
173+
condition:boolean;
174+
expr;
175+
constructor() {
176+
this.condition = true;
177+
this.expr = {'foo': true, 'bar': false};
178+
}
179+
}

tools/transpiler/src/outputgeneration/DartParseTreeWriter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,4 +531,4 @@ export class DartParseTreeWriter extends JavaScriptParseTreeWriter {
531531
}
532532

533533
// see: https://www.dartlang.org/docs/dart-up-and-running/ch02.html for a full list.
534-
const DART_RESERVED_WORDS = ['if', 'switch', 'for'];
534+
const DART_RESERVED_WORDS = ['if', 'switch', 'for', 'class'];

0 commit comments

Comments
 (0)