X Tutup
Skip to content
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
42 changes: 31 additions & 11 deletions modules/angular2/src/directives/ng_class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,24 @@ import {ListWrapper, StringMapWrapper, isListLikeIterable} from 'angular2/src/fa
@Directive({
selector: '[ng-class]',
lifecycle: [LifecycleEvent.onCheck, LifecycleEvent.onDestroy],
properties: ['rawClass: ng-class']
properties: ['rawClass: ng-class', 'initialClasses: class']
})
export class NgClass {
private _differ: any;
private _mode: string;
_rawClass;
private _initialClasses = [];
private _rawClass;

constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {}

set initialClasses(v) {
this._applyInitialClasses(true);
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
this._applyInitialClasses(false);
this._applyClasses(this._rawClass, false);
}

set rawClass(v) {
this._cleanupClasses(this._rawClass);

Expand All @@ -59,6 +67,8 @@ export class NgClass {
this._differ = this._keyValueDiffers.find(v).create(null);
this._mode = 'keyValue';
}
} else {
this._differ = null;
}
}

Expand All @@ -78,15 +88,8 @@ export class NgClass {
onDestroy(): void { this._cleanupClasses(this._rawClass); }

private _cleanupClasses(rawClassVal): void {
if (isPresent(rawClassVal)) {
if (isListLikeIterable(rawClassVal)) {
ListWrapper.forEach(rawClassVal, (className) => { this._toggleClass(className, false); });
} else {
StringMapWrapper.forEach(rawClassVal, (expVal, className) => {
if (expVal) this._toggleClass(className, false);
});
}
}
this._applyClasses(rawClassVal, true);
this._applyInitialClasses(false);
}

private _applyKeyValueChanges(changes: any): void {
Expand All @@ -104,6 +107,23 @@ export class NgClass {
changes.forEachRemovedItem((record) => { this._toggleClass(record.item, false); });
}

private _applyInitialClasses(isCleanup: boolean) {
ListWrapper.forEach(this._initialClasses,
(className) => { this._toggleClass(className, !isCleanup); });
}

private _applyClasses(rawClassVal, isCleanup: boolean) {
if (isPresent(rawClassVal)) {
if (isListLikeIterable(rawClassVal)) {
ListWrapper.forEach(rawClassVal, (className) => this._toggleClass(className, !isCleanup));
} else {
StringMapWrapper.forEach(rawClassVal, (expVal, className) => {
if (expVal) this._toggleClass(className, !isCleanup);
});
}
}
}

private _toggleClass(className: string, enabled): void {
this._renderer.setElementClass(this._ngEl, className, enabled);
}
Expand Down
1 change: 1 addition & 0 deletions modules/angular2/src/dom/browser_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter {

@override
Map<String, String> get attrToPropMap => const <String, String>{
'class': 'className',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
Expand Down
7 changes: 6 additions & 1 deletion modules/angular2/src/dom/browser_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import {isBlank, isPresent, global} from 'angular2/src/facade/lang';
import {setRootDomAdapter} from './dom_adapter';
import {GenericBrowserDomAdapter} from './generic_browser_adapter';

var _attrToPropMap = {'innerHtml': 'innerHTML', 'readonly': 'readOnly', 'tabindex': 'tabIndex'};
var _attrToPropMap = {
'class': 'className',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex'
};

const DOM_KEY_LOCATION_NUMPAD = 3;

Expand Down
3 changes: 3 additions & 0 deletions modules/angular2/src/dom/parse5_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {BaseException, isPresent, isBlank, global} from 'angular2/src/facade/lan
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';

var _attrToPropMap = {
'class': 'className',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
Expand All @@ -37,6 +38,8 @@ export class Parse5DomAdapter extends DomAdapter {
setProperty(el: /*element*/ any, name: string, value: any) {
if (name === 'innerHTML') {
this.setInnerHTML(el, value);
} else if (name === 'className') {
el.attribs["class"] = el.className = value;
} else {
el[name] = value;
}
Expand Down
20 changes: 20 additions & 0 deletions modules/angular2/test/core/compiler/integration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,26 @@ export function main() {
});
}));

it('should consume binding to className using class alias',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(
MyComp,
new viewAnn.View({template: '<div class="initial" [class]="ctxProp"></div>'}))

.createAsync(MyComp)
.then((rootTC) => {
var nativeEl = rootTC.componentViewChildren[0].nativeElement;
rootTC.componentInstance.ctxProp = 'foo bar';
rootTC.detectChanges();

expect(nativeEl).toHaveCssClass('foo');
expect(nativeEl).toHaveCssClass('bar');
expect(nativeEl).not.toHaveCssClass('initial');

async.done();
});
}));

it('should consume directive watch expression change.',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var tpl = '<div>' +
Expand Down
190 changes: 146 additions & 44 deletions modules/angular2/test/directives/ng_class_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ export function main() {
rootTC.componentInstance.objExpr = {baz: true};
detectChangesAndCheck(rootTC, 'ng-binding baz');

async.done();
});
}));

it('should remove active classes when expression evaluates to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ng-class]="objExpr"></div>';

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
detectChangesAndCheck(rootTC, 'ng-binding foo');

rootTC.componentInstance.objExpr = null;
detectChangesAndCheck(rootTC, 'ng-binding');

rootTC.componentInstance.objExpr = {'foo': false, 'bar': true};
detectChangesAndCheck(rootTC, 'ng-binding bar');

async.done();
});
}));
Expand Down Expand Up @@ -181,6 +200,22 @@ export function main() {
rootTC.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(rootTC, 'ng-binding bar');

async.done();
});
}));

it('should take initial classes into account when a reference changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="foo" [ng-class]="arrExpr"></div>';

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
detectChangesAndCheck(rootTC, 'foo ng-binding');

rootTC.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(rootTC, 'ng-binding foo bar');

async.done();
});
}));
Expand Down Expand Up @@ -220,7 +255,6 @@ export function main() {
});
}));


it('should remove active classes when switching from string to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ng-class]="strExpr"></div>`;
Expand All @@ -236,65 +270,133 @@ export function main() {
async.done();
});
}));

it('should take initial classes into account when switching from string to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div class="foo" [ng-class]="strExpr"></div>`;

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
detectChangesAndCheck(rootTC, 'foo ng-binding');

rootTC.componentInstance.strExpr = null;
detectChangesAndCheck(rootTC, 'ng-binding foo');

async.done();
});
}));

});

it('should remove active classes when expression evaluates to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ng-class]="objExpr"></div>';
describe('cooperation with other class-changing constructs', () => {

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
detectChangesAndCheck(rootTC, 'ng-binding foo');
it('should co-operate with the class attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ng-class]="objExpr" class="init foo"></div>';

rootTC.componentInstance.objExpr = null;
detectChangesAndCheck(rootTC, 'ng-binding');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(rootTC, 'init foo ng-binding bar');

rootTC.componentInstance.objExpr = {'foo': false, 'bar': true};
detectChangesAndCheck(rootTC, 'ng-binding bar');
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(rootTC, 'init ng-binding bar');

async.done();
});
}));
rootTC.componentInstance.objExpr = null;
detectChangesAndCheck(rootTC, 'init ng-binding foo');

it('should co-operate with the class attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ng-class]="objExpr" class="init foo"></div>';
async.done();
});
}));

it('should co-operate with the interpolated class attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ng-class]="objExpr" class="{{'init foo'}}"></div>`;

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(rootTC, `{{'init foo'}} ng-binding init foo bar`);

StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(rootTC, `{{'init foo'}} ng-binding init bar`);

rootTC.componentInstance.objExpr = null;
detectChangesAndCheck(rootTC, `{{'init foo'}} ng-binding init foo`);

async.done();
});
}));

it('should co-operate with the class attribute and binding to it',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ng-class]="objExpr" class="init" [class]="'foo'"></div>`;

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(rootTC, `init ng-binding foo bar`);

StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(rootTC, `init ng-binding bar`);

rootTC.componentInstance.objExpr = null;
detectChangesAndCheck(rootTC, `init ng-binding foo`);

async.done();
});
}));

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(rootTC, 'init foo ng-binding bar');
it('should co-operate with the class attribute and class.name binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div class="init foo" [ng-class]="objExpr" [class.baz]="condition"></div>';

StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(rootTC, 'init ng-binding bar');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
detectChangesAndCheck(rootTC, 'init foo ng-binding baz');

async.done();
});
}));
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(rootTC, 'init foo ng-binding baz bar');

it('should co-operate with the class attribute and class.name binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="init foo" [ng-class]="objExpr" [class.baz]="condition"></div>';
StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(rootTC, 'init ng-binding baz bar');

tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
detectChangesAndCheck(rootTC, 'init foo ng-binding baz');
rootTC.componentInstance.condition = false;
detectChangesAndCheck(rootTC, 'init ng-binding bar');

StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(rootTC, 'init foo ng-binding baz bar');
async.done();
});
}));

StringMapWrapper.set(rootTC.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(rootTC, 'init ng-binding baz bar');
it('should co-operate with initial class and class attribute binding when binding changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="init" [ng-class]="objExpr" [class]="strExpr"></div>';

rootTC.componentInstance.condition = false;
detectChangesAndCheck(rootTC, 'init ng-binding bar');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((rootTC) => {
detectChangesAndCheck(rootTC, 'init ng-binding foo');

StringMapWrapper.set(rootTC.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(rootTC, 'init ng-binding foo bar');

rootTC.componentInstance.strExpr = 'baz';
detectChangesAndCheck(rootTC, 'init ng-binding bar baz foo');

async.done();
});
}));
rootTC.componentInstance.objExpr = null;
detectChangesAndCheck(rootTC, 'init ng-binding baz');

async.done();
});
}));

});
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ export function main() {
var pv = builder.build(new DomElementSchemaRegistry(), templateCloner);
expect(pv.elementBinders[0].propertyBindings[0].property).toEqual('readOnly');
});

it('should normalize "class" to "className"', () => {
builder.bindElement(el('<div></div>')).bindProperty('class', emptyExpr());
var pv = builder.build(new DomElementSchemaRegistry(), templateCloner);
expect(pv.elementBinders[0].propertyBindings[0].property).toEqual('className');
});
});

describe('property binding', () => {
Expand Down
X Tutup