feat(forms): added support for status classes
This commit is contained in:
parent
96cadcc29e
commit
3baf815d76
|
@ -105,7 +105,10 @@ class BindingRecordsCreator {
|
|||
if (directiveRecord.callOnCheck) {
|
||||
ListWrapper.push(bindings, BindingRecord.createDirectiveOnCheck(directiveRecord));
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < directiveBinders.length; i++) {
|
||||
var directiveBinder = directiveBinders[i];
|
||||
// host properties
|
||||
MapWrapper.forEach(directiveBinder.hostPropertyBindings, (astWithSource, propertyName) => {
|
||||
var dirIndex = new DirectiveIndex(boundElementIndex, i);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {ElementRef, Directive} from 'angular2/angular2';
|
||||
import {Renderer} from 'angular2/src/render/api';
|
||||
import {Directive} from 'angular2/angular2';
|
||||
import {ControlDirective} from './control_directive';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
|
||||
|
@ -18,23 +17,28 @@ import {ControlValueAccessor} from './control_value_accessor';
|
|||
selector:
|
||||
'input[type=checkbox][ng-control],input[type=checkbox][ng-form-control],input[type=checkbox][ng-model]',
|
||||
hostListeners: {'change': 'onChange($event.target.checked)', 'blur': 'onTouched()'},
|
||||
hostProperties: {'checked': 'checked'}
|
||||
hostProperties: {
|
||||
'checked': 'checked',
|
||||
'cd.control?.untouched == true': 'class.ng-untouched',
|
||||
'cd.control?.touched == true': 'class.ng-touched',
|
||||
'cd.control?.pristine == true': 'class.ng-pristine',
|
||||
'cd.control?.dirty == true': 'class.ng-dirty',
|
||||
'cd.control?.valid == true': 'class.ng-valid',
|
||||
'cd.control?.valid == false': 'class.ng-invalid'
|
||||
}
|
||||
})
|
||||
export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
checked: boolean;
|
||||
onChange: Function;
|
||||
onTouched: Function;
|
||||
|
||||
constructor(cd: ControlDirective, private _elementRef: ElementRef, private _renderer: Renderer) {
|
||||
constructor(private cd: ControlDirective) {
|
||||
this.onChange = (_) => {};
|
||||
this.onTouched = (_) => {};
|
||||
cd.valueAccessor = this;
|
||||
}
|
||||
|
||||
writeValue(value) {
|
||||
this._renderer.setElementProperty(this._elementRef.parentView.render,
|
||||
this._elementRef.boundElementIndex, 'checked', value)
|
||||
}
|
||||
writeValue(value) { this.checked = value; }
|
||||
|
||||
registerOnChange(fn): void { this.onChange = fn; }
|
||||
registerOnTouched(fn): void { this.onTouched = fn; }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {Validators} from '../validators';
|
||||
import {Control} from '../model';
|
||||
|
||||
/**
|
||||
* A directive that bind a [ng-control] object to a DOM element.
|
||||
|
@ -12,6 +13,7 @@ export class ControlDirective {
|
|||
validator: Function;
|
||||
|
||||
get path(): List<string> { return null; }
|
||||
get control(): Control { return null; }
|
||||
constructor() { this.validator = Validators.nullValidator; }
|
||||
|
||||
viewToModelUpdate(newValue: any): void {}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {FORWARD_REF, Binding, Inject} from 'angular2/di';
|
|||
import {ControlContainerDirective} from './control_container_directive';
|
||||
import {ControlDirective} from './control_directive';
|
||||
import {controlPath} from './shared';
|
||||
import {Control} from '../model';
|
||||
|
||||
const controlNameBinding =
|
||||
CONST_EXPR(new Binding(ControlDirective, {toAlias: FORWARD_REF(() => ControlNameDirective)}));
|
||||
|
@ -84,6 +85,7 @@ export class ControlNameDirective extends ControlDirective {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
onDestroy() { this.formDirective.removeControl(this); }
|
||||
|
||||
viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.ngModel, newValue); }
|
||||
|
@ -91,4 +93,6 @@ export class ControlNameDirective extends ControlDirective {
|
|||
get path(): List<string> { return controlPath(this.name, this._parent); }
|
||||
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
|
||||
get control(): Control { return this.formDirective.getControl(this); }
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import {ElementRef, Directive} from 'angular2/angular2';
|
||||
import {Renderer} from 'angular2/src/render/api';
|
||||
import {Directive} from 'angular2/angular2';
|
||||
import {ControlDirective} from './control_directive';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
|
||||
|
@ -24,24 +23,30 @@ import {ControlValueAccessor} from './control_value_accessor';
|
|||
'input': 'onChange($event.target.value)',
|
||||
'blur': 'onTouched()'
|
||||
},
|
||||
hostProperties: {'value': 'value'}
|
||||
hostProperties: {
|
||||
'value': 'value',
|
||||
'cd.control?.untouched == true': 'class.ng-untouched',
|
||||
'cd.control?.touched == true': 'class.ng-touched',
|
||||
'cd.control?.pristine == true': 'class.ng-pristine',
|
||||
'cd.control?.dirty == true': 'class.ng-dirty',
|
||||
'cd.control?.valid == true': 'class.ng-valid',
|
||||
'cd.control?.valid == false': 'class.ng-invalid'
|
||||
}
|
||||
})
|
||||
export class DefaultValueAccessor implements ControlValueAccessor {
|
||||
value = null;
|
||||
onChange: Function;
|
||||
onTouched: Function;
|
||||
|
||||
constructor(cd: ControlDirective, private _elementRef: ElementRef, private _renderer: Renderer) {
|
||||
constructor(private cd: ControlDirective) {
|
||||
this.onChange = (_) => {};
|
||||
this.onTouched = (_) => {};
|
||||
cd.valueAccessor = this;
|
||||
}
|
||||
|
||||
writeValue(value) {
|
||||
this._renderer.setElementProperty(this._elementRef.parentView.render,
|
||||
this._elementRef.boundElementIndex, 'value', value)
|
||||
}
|
||||
writeValue(value) { this.value = value; }
|
||||
|
||||
registerOnChange(fn): void { this.onChange = fn; }
|
||||
|
||||
registerOnTouched(fn): void { this.onTouched = fn; }
|
||||
}
|
|
@ -47,12 +47,12 @@ const formControlBinding =
|
|||
@Directive({
|
||||
selector: '[ng-form-control]',
|
||||
hostInjector: [formControlBinding],
|
||||
properties: ['control: ng-form-control', 'model: ng-model'],
|
||||
properties: ['form: ng-form-control', 'model: ng-model'],
|
||||
events: ['ngModel'],
|
||||
lifecycle: [onChange]
|
||||
})
|
||||
export class FormControlDirective extends ControlDirective {
|
||||
control: Control;
|
||||
form: Control;
|
||||
ngModel: EventEmitter;
|
||||
_added: boolean;
|
||||
model: any;
|
||||
|
@ -65,14 +65,18 @@ export class FormControlDirective extends ControlDirective {
|
|||
|
||||
onChange(c) {
|
||||
if (!this._added) {
|
||||
setUpControl(this.control, this);
|
||||
this.control.updateValidity();
|
||||
setUpControl(this.form, this);
|
||||
this.form.updateValidity();
|
||||
this._added = true;
|
||||
}
|
||||
if (StringMapWrapper.contains(c, "model")) {
|
||||
this.control.updateValue(this.model);
|
||||
this.form.updateValue(this.model);
|
||||
}
|
||||
}
|
||||
|
||||
get control(): Control { return this.form; }
|
||||
|
||||
get path(): List<string> { return []; }
|
||||
|
||||
viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.ngModel, newValue); }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {ControlDirective} from './control_directive';
|
||||
import {ControlGroupDirective} from './control_group_directive';
|
||||
import {Control} from '../model';
|
||||
|
||||
export interface FormDirective {
|
||||
addControl(dir: ControlDirective): void;
|
||||
removeControl(dir: ControlDirective): void;
|
||||
getControl(dir: ControlDirective): Control;
|
||||
addControlGroup(dir: ControlGroupDirective): void;
|
||||
removeControlGroup(dir: ControlGroupDirective): void;
|
||||
updateModel(dir: ControlDirective, value: any): void;
|
||||
|
|
|
@ -81,6 +81,8 @@ export class FormModelDirective extends ControlContainerDirective implements For
|
|||
ListWrapper.push(this.directives, dir);
|
||||
}
|
||||
|
||||
getControl(dir: ControlDirective): Control { return <Control>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: ControlDirective): void { ListWrapper.remove(this.directives, dir); }
|
||||
|
||||
addControlGroup(dir: ControlGroupDirective) {}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {ElementRef, Directive} from 'angular2/angular2';
|
||||
import {Renderer} from 'angular2/src/render/api';
|
||||
import {Directive} from 'angular2/angular2';
|
||||
import {ControlDirective} from './control_directive';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
|
||||
|
@ -23,24 +22,29 @@ import {ControlValueAccessor} from './control_value_accessor';
|
|||
'input': 'onChange($event.target.value)',
|
||||
'blur': 'onTouched()'
|
||||
},
|
||||
hostProperties: {'value': 'value'}
|
||||
hostProperties: {
|
||||
'value': 'value',
|
||||
'cd.control?.untouched == true': 'class.ng-untouched',
|
||||
'cd.control?.touched == true': 'class.ng-touched',
|
||||
'cd.control?.pristine == true': 'class.ng-pristine',
|
||||
'cd.control?.dirty == true': 'class.ng-dirty',
|
||||
'cd.control?.valid == true': 'class.ng-valid',
|
||||
'cd.control?.valid == false': 'class.ng-invalid'
|
||||
}
|
||||
})
|
||||
export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
value = null;
|
||||
onChange: Function;
|
||||
onTouched: Function;
|
||||
|
||||
constructor(cd: ControlDirective, private _elementRef: ElementRef, private _renderer: Renderer) {
|
||||
constructor(private cd: ControlDirective) {
|
||||
this.onChange = (_) => {};
|
||||
this.onTouched = (_) => {};
|
||||
this.value = '';
|
||||
cd.valueAccessor = this;
|
||||
}
|
||||
|
||||
writeValue(value) {
|
||||
this._renderer.setElementProperty(this._elementRef.parentView.render,
|
||||
this._elementRef.boundElementIndex, 'value', value)
|
||||
}
|
||||
writeValue(value) { this.value = value; }
|
||||
|
||||
registerOnChange(fn): void { this.onChange = fn; }
|
||||
registerOnTouched(fn): void { this.onTouched = fn; }
|
||||
|
|
|
@ -43,6 +43,8 @@ export class TemplateDrivenFormDirective extends ControlContainerDirective imple
|
|||
});
|
||||
}
|
||||
|
||||
getControl(dir: ControlDirective): Control { return <Control>this.form.find(dir.path); }
|
||||
|
||||
removeControl(dir: ControlDirective): void {
|
||||
this._later(_ => {
|
||||
var c = this._findContainer(dir.path);
|
||||
|
|
|
@ -172,7 +172,7 @@ export function main() {
|
|||
controlDir.valueAccessor = new DummyControlValueAccessor();
|
||||
|
||||
control = new Control(null);
|
||||
controlDir.control = control;
|
||||
controlDir.form = control;
|
||||
});
|
||||
|
||||
it("should set up validator", () => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
xit
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {TestBed} from 'angular2/src/test_lib/test_bed';
|
||||
import {NgIf} from 'angular2/directives';
|
||||
|
||||
|
@ -561,6 +562,7 @@ export function main() {
|
|||
.then((view) => {
|
||||
view.detectChanges();
|
||||
tick();
|
||||
view.detectChanges();
|
||||
|
||||
var input = view.querySelector("input");
|
||||
expect(input.value).toEqual("oldValue");
|
||||
|
@ -599,6 +601,105 @@ export function main() {
|
|||
flushMicrotasks();
|
||||
})));
|
||||
});
|
||||
|
||||
|
||||
describe("setting status classes", () => {
|
||||
it("should work with single fields",
|
||||
inject([TestBed], fakeAsync(tb => {
|
||||
var form = new Control("", Validators.required);
|
||||
var ctx = MyComp.create({form: form});
|
||||
|
||||
var t = `<div><input type="text" [ng-form-control]="form"></div>`;
|
||||
|
||||
tb.createView(MyComp, {context: ctx, html: t})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
|
||||
var input = view.querySelector("input");
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-untouched", "ng-pristine", "ng-invalid"]);
|
||||
|
||||
dispatchEvent(input, "blur");
|
||||
view.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-pristine", "ng-invalid", "ng-touched"]);
|
||||
|
||||
input.value = "updatedValue";
|
||||
dispatchEvent(input, "change");
|
||||
view.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-touched", "ng-dirty", "ng-valid"]);
|
||||
tick();
|
||||
});
|
||||
flushMicrotasks();
|
||||
})));
|
||||
|
||||
it("should work with complex model-driven forms",
|
||||
inject([TestBed], fakeAsync(tb => {
|
||||
var form = new ControlGroup({"name": new Control("", Validators.required)});
|
||||
var ctx = MyComp.create({form: form});
|
||||
|
||||
var t =
|
||||
`<form [ng-form-model]="form"><input type="text" ng-control="name"></form>`;
|
||||
|
||||
tb.createView(MyComp, {context: ctx, html: t})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
|
||||
var input = view.querySelector("input");
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-untouched", "ng-pristine", "ng-invalid"]);
|
||||
|
||||
dispatchEvent(input, "blur");
|
||||
view.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-pristine", "ng-invalid", "ng-touched"]);
|
||||
|
||||
input.value = "updatedValue";
|
||||
dispatchEvent(input, "change");
|
||||
view.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-touched", "ng-dirty", "ng-valid"]);
|
||||
tick();
|
||||
});
|
||||
flushMicrotasks();
|
||||
})));
|
||||
|
||||
it("should work with ng-model",
|
||||
inject([TestBed], fakeAsync(tb => {
|
||||
var ctx = MyComp.create({name: ""});
|
||||
|
||||
var t = `<div><input [(ng-model)]="name" required></div>`;
|
||||
|
||||
tb.createView(MyComp, {context: ctx, html: t})
|
||||
.then((view) => {
|
||||
view.detectChanges();
|
||||
|
||||
var input = view.querySelector("input");
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-untouched", "ng-pristine", "ng-invalid"]);
|
||||
|
||||
dispatchEvent(input, "blur");
|
||||
view.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-pristine", "ng-invalid", "ng-touched"]);
|
||||
|
||||
input.value = "updatedValue";
|
||||
dispatchEvent(input, "change");
|
||||
view.detectChanges();
|
||||
|
||||
expect(DOM.classList(input))
|
||||
.toEqual(["ng-binding", "ng-touched", "ng-dirty", "ng-valid"]);
|
||||
tick();
|
||||
});
|
||||
flushMicrotasks();
|
||||
})));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -210,9 +210,7 @@ export function main() {
|
|||
g = new ControlGroup({"one": c});
|
||||
});
|
||||
|
||||
it("should be false after creating a control", () => {
|
||||
expect(g.dirty).toEqual(false);
|
||||
});
|
||||
it("should be false after creating a control", () => { expect(g.dirty).toEqual(false); });
|
||||
|
||||
it("should be false after changing the value of the control", () => {
|
||||
c.markAsDirty();
|
||||
|
@ -449,9 +447,7 @@ export function main() {
|
|||
a = new ControlArray([c]);
|
||||
});
|
||||
|
||||
it("should be false after creating a control", () => {
|
||||
expect(a.dirty).toEqual(false);
|
||||
});
|
||||
it("should be false after creating a control", () => { expect(a.dirty).toEqual(false); });
|
||||
|
||||
it("should be false after changing the value of the control", () => {
|
||||
c.markAsDirty();
|
||||
|
|
Loading…
Reference in New Issue