Skip to content
This repository has been archived by the owner on Feb 2, 2019. It is now read-only.

Commit

Permalink
feat(form): use md-message/md-messages for form validation errors
Browse files Browse the repository at this point in the history
  - support declaratively specifying validators in input basic usage example.
  - add md-messages directive for specifying a group to hold form messages.
  - add md-message to decorate elements inside of md-messages container to be associated with a particular error.
  - refactor messages and validation out of input/ path and into form/ path, which seems more appropriate.
  • Loading branch information
justindujardin committed Jan 10, 2016
1 parent ebe8c36 commit 14e36f4
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 28 deletions.
11 changes: 4 additions & 7 deletions examples/components/input/basic_usage.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,13 @@

<md-input-container class="md-block" flex-gt-sm>
<label>Postal Code</label>
<!--<br>TODO: remove this: {{postalCode.errors | json}}-->
<input md-input ngControl="postalCode" [(value)]="user.postalCode" placeholder="12345"
required mdPattern="^[0-9]{5}$" #postalCode="ngForm" mdMaxLength="5">

<div md-messages role="alert" multiple [hidden]="postalCode.valid || postalCode.pristine">
<div [hidden]="!postalCode.errors?.required" md-message>You must supply a postal code.</div>
<div [hidden]="!postalCode.errors?.mdPattern" md-message>That doesn't look like a valid postal code.</div>
<div [hidden]="!postalCode.errors?.mdMaxLength" md-message>
Don't use the long version silly...we don't need to be that specific...
</div>
<div [md-messages]="postalCode" role="alert">
<div md-message="required">You must supply a postal code.</div>
<div md-message="mdPattern">That doesn't look like a valid postal code.</div>
<div md-message="mdMaxLength">Don't use the long version silly...we don't need to be that specific...</div>
</div>
</md-input-container>
</div>
Expand Down
12 changes: 7 additions & 5 deletions ng2-material/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ export * from './components/grid_list/grid_list';
import {MdIcon} from './components/icon/icon';
export * from './components/icon/icon';

import {
MdInput, MdInputContainer,
MdPatternValidator, MdMaxLengthValidator,
INPUT_VALIDATORS
} from './components/input/input';
import {MdPatternValidator, MdMaxLengthValidator,INPUT_VALIDATORS} from './components/form/validators';
export * from './components/form/validators';
import {MdMessage, MdMessages} from './components/form/messages';
export * from './components/form/messages';

import {MdInput, MdInputContainer} from './components/input/input';
export * from './components/input/input';

import {MdList, MdListItem} from './components/list/list';
Expand Down Expand Up @@ -70,6 +71,7 @@ export const MATERIAL_DIRECTIVES: Type[] = CONST_EXPR([
MdGridList, MdGridTile,
MdIcon,
MdInput, MdInputContainer, MdPatternValidator, MdMaxLengthValidator,
MdMessage, MdMessages,
MdList, MdListItem,
MdPeekaboo,
MdProgressLinear,
Expand Down
83 changes: 83 additions & 0 deletions ng2-material/components/form/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {CONST_EXPR} from "angular2/src/facade/lang";
import {NG_VALIDATORS} from "angular2/common";
import {
Attribute, Input, Provider, Directive, Optional, SkipSelf, Host, OnDestroy, OnInit,
ContentChildren, QueryList, Query, AfterContentInit
} from "angular2/core";
import {Validator, NgFormModel, NgControlName} from "angular2/common";
import {Control} from "angular2/common";
import {isPresent} from "angular2/src/facade/lang";


@Directive({
selector: '[md-message]',
host: {
'[hidden]': 'okay'
}
})
export class MdMessage {
@Input('md-message') errorKey: string;

okay: boolean = true;

constructor(@Attribute('md-message') pattern: any) {
if (isPresent(pattern)) {
this.errorKey = pattern;
}
}
}


@Directive({
selector: '[md-messages]',
host: {
'md-messages': '',
'[hidden]': 'valid || !property?.touched',
'[class.md-valid]': 'valid && property?.touched',
'[class.md-invalid]': '!valid && property?.touched'
}
})
export class MdMessages implements OnInit, OnDestroy {

@Input('md-messages') property: string|NgControlName;

valid: boolean;

constructor(@Query(MdMessage) public messages: QueryList<MdMessage>,
@Optional() @SkipSelf() @Host() public form: NgFormModel,
@Attribute('md-messages') pattern: any) {
if (isPresent(pattern)) {
this.property = pattern;
}
}

private _unsubscribe: any = null;

ngOnInit() {
if (this.property instanceof NgControlName) {
let ctrl: NgControlName = <NgControlName>this.property;
this.form = ctrl.formDirective;
this._unsubscribe = (<any>ctrl).update.subscribe(this._valueChanged.bind(this));
}
else {
if (!this.form) {
throw new Error('md-messages cannot bind to text property without a parent NgFormModel');
}
console.log('init formbuilder property: ' + this.property);
}
}

ngOnDestroy(): any {
this._unsubscribe();
}

private _valueChanged(newValue: string) {
let ctrl: NgControlName = <NgControlName>this.property;
this.valid = !ctrl.errors;
if (ctrl.errors) {
this.messages.toArray().forEach((m: MdMessage) => {
m.okay = !isPresent(ctrl.errors[m.errorKey]);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ const PATTERN_VALIDATOR = CONST_EXPR(new Provider(NG_VALIDATORS, {
})
export class MdPatternValidator implements Validator {

/**
* Static method that returns a validator function for use
* with {@see FormBuilder}.
* @param pattern The regular expression to match.
* @returns A Validator function that may be used with
*/
static match(pattern:string):Function {
return new MdPatternValidator(pattern).validate;
}

@Input('mdPattern') mdPattern: string;

constructor(@Attribute('mdPattern') pattern: any) {
Expand Down
23 changes: 7 additions & 16 deletions ng2-material/components/input/input.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
Directive, Attribute, Host, SkipSelf, AfterContentInit, ElementRef, forwardRef, OnChanges, ContentChild,
Query, QueryList, OnInit, Optional
Query, QueryList, Optional
} from 'angular2/core';

import {NgControlName, FORM_PROVIDERS} from 'angular2/common';
Expand All @@ -11,9 +11,6 @@ import {isPresent} from 'angular2/src/facade/lang';
import {DOM} from "angular2/src/platform/dom/dom_adapter";
import {TimerWrapper} from "angular2/src/facade/async";

export * from './input_validators';


// TODO(jd): <select> hasFocus/hasValue classes
// TODO(jelbourn): validation (will depend on Forms API).
// TODO(jelbourn): textarea resizing
Expand All @@ -32,7 +29,7 @@ export * from './input_validators';
},
providers: [FORM_PROVIDERS]
})
export class MdInput implements OnInit {
export class MdInput {
@Input('value')
_value: string;

Expand All @@ -53,22 +50,12 @@ export class MdInput implements OnInit {
mdFocusChange: EventEmitter<any> = new EventEmitter();

constructor(@Attribute('value') value: string,
@Attribute('id') id: string,
@Optional() private _ctrl: NgControlName) {
@Attribute('id') id: string) {
if (isPresent(value)) {
this.value = value;
}
}

ngOnInit() {
if(this._ctrl){
this._ctrl.formDirective.control.statusChanges.subscribe((c) => {
console.log(c);
});
}
console.log(this._ctrl);
}

setHasFocus(hasFocus: boolean) {
ObservableWrapper.callEmit(this.mdFocusChange, hasFocus);
}
Expand Down Expand Up @@ -116,6 +103,10 @@ export class MdInputContainer implements AfterContentInit, OnChanges {
return;
}

// TODO(jd): :sob: what is the correct way to update these variables after the component initializes?
// any time I do it directly here, debug mode complains about values changing after being checked. I
// need to wait until the content has been initialized so that `_input` is there
// For now, just wrap it in a setTimeout to let the change detection finish up, and then set the values...
TimerWrapper.setTimeout(() => this.ngOnChanges({}), 0);

// Listen to input changes and focus events so that we can apply the appropriate CSS
Expand Down

0 comments on commit 14e36f4

Please sign in to comment.