React Form validation displaying error - forms

I am using React-Validation-Mixin together with Joi and Joi-Validation-Strategy to do some validations on a React Step/Wizard Form.
I have a parent FormStart Element that receives the state of its FormStep children through props.
The validation correctly signals that the input is required, but when I write a correct number in the browser (5 numbers as in PLZ/ZIP-Code), it will still signal that the input is invalid, even though the zip state shows a correct 5-digit number, so the next button never takes me to the next Form step.
class FormStart extends Component {
constructor(props) {
super(props);
this.state = {
step: 1,
zip: ""
}
this.goToNext = this.goToNext.bind(this);
}
goToNext() {
const { step } = this.state;
if (step !== 10) {
this.setState({ step: step + 1 });
if (step == 9) {
const values = {
zip: this.state.zip,
};
console.log(values);
// submit `values` to the server here.
}
}
}
handleChange(field) {
return (evt) => this.setState({ [field]: evt.target.value });
}
render(){
switch (this.state.step) {
case 1:
return <FormButton
onSubmit={this.goToNext}
/>;
//omitting the other 8 cases
case 9:
return <FormStep7
onSubmit={this.goToNext}
zip={this.state.zip}
onZipChange={this.handleChange('zip')}
/>;
case 10:
return <FormSuccess/>;
}
}
}
export default FormStart;
The React console shows that the zip state is correctly changed, and the Validation object also receives the same correct 5-digit zip and still holds the correct value onBlur.
class FormStep7 extends Component {
constructor(props) {
super(props);
this.validatorTypes = {
PLZ: Joi.number().min(5).max(5).required().label('PLZ').options({
language: {
number: {
base: 'wird benötigt',
min: 'muss {{limit}} Nummern enthalten',
max: 'muss {{limit}} Nummern enthalten'
}
}
})
};
this.getValidatorData = this.getValidatorData.bind(this);
this.getClasses = this.getClasses.bind(this);
this.renderHelpText = this.renderHelpText.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
getValidatorData() {
return {
PLZ: this.props.zip
};
}
getClasses(field) {
return classnames({
'form-control': true,
'has-error': !this.props.isValid(field)
});
}
renderHelpText(message) {
return (
<span className='help-block'>{message}</span>
);
}
handleSubmit(evt) {
evt.preventDefault();
const onValidate = (error) => {
if (error) {
//form has errors; do not submit
} else {
this.props.onSubmit();
}
};
this.props.validate(onValidate);
}
render() {
return (
<form role="form" onSubmit={this.handleSubmit}>
<div className='row'>
<div className="col-md-10 col-md-offset-1">
<div className='form-group'>
<label htmlFor="zip">
Einsatzort
</label>
<br />
<input className={this.getClasses('PLZ')} id="PLZ" placeholder="Meine PLZ" type="text" onChange={this.props.onZipChange} onBlur={this.props.handleValidation('PLZ')} value={this.props.zip} />
{this.renderHelpText(this.props.getValidationMessages('PLZ'))}
</div>
</div>
</div>
<div className='row'>
<div className="col-md-10 col-md-offset-1">
<button className="btn btn-green btn-block">Next</button>
</div>
</div>
</div>
</form>
);
}
}
FormStep7.propTypes = {
errors: PropTypes.object,
validate: PropTypes.func,
isValid: PropTypes.func,
handleValidation: PropTypes.func,
getValidationMessages: PropTypes.func,
clearValidations: PropTypes.func
};
export default validation(strategy)(FormStep7);
What am I doing wrong?

I found out that the issue was on Joi.number(). I changed the validation to match a Regex String pattern and then it worked.
this.validatorTypes = {
PLZ: Joi.string().regex(/^[0-9]{5}$/).label('PLZ').options({
language: {
string: {
regex: {
base: "mit 5 Nummern wird benötigt"
}
}
}
})
};

Related

How to prevent repeating code of form validation

I created the form for multiple inputs, where the specific input data shall be validated at the time of data entry and once again for all data just before the submission of the form to the backend.
The conditions to submit: all fields are mandatory and the data is valid.
My program works, but I don't like that I'm repeating the validation code in 2 places: in ErrorOutput and hadleSubmit.
In ErrorOutput I check the data and, if necessary, display an error message.
In handleSubmit I just check the data without displaying of error message and if all data is valid, I confirm submitting.
How can I improve my example to prevent the repetition of this code, but the data validation was also at the time of data entry and before submission?
import React from 'react'
import { render } from 'react-dom'
const ErrorOutput = props => {
let name = props.name
let inputValue = props.case
let submit = props.submit
// Data validation
if (name === 'firstName') {
if (!inputValue.match(/^[a-zA-Z]+$/) && inputValue.length > 0) {
return <span>Letters only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
if (name === 'telNo') {
if(!inputValue.match(/^[0-9]+$/) && inputValue.length > 0) {
return <span>Numbers only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false
}
}
handleSubmit(e){
e.preventDefault()
let submit = true
let error = true
const { firstName, telNo } = this.state
this.setState ({submit: submit})
// Repeat the data validation before submission
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
error = true
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
error = true
} else {
error = false
}
// Submited if all data is valid
if (!error) {
// send data
return alert('Success!')
}
}
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>
First name:
</label>
<input
type='text'
name ='firstName'
value = {this.state.firstName}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.firstName} name={'firstName'} submit = {this.state.submit} />
</div>
<div>
<label>
Phone number:
</label>
<input
type='tel'
name ='telNo'
value = {this.state.telNo}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.telNo} name={'telNo'} submit = {this.state.submit} />
</div>
<button>
Submit
</button>
</form>
)
}
}
render(
<App />,
document.getElementById('root')
)
You could extract a FormItem component:
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
<ErrorOutput
case={this.props.input.value}
name={this.props.input.name}
submit={this.props.onSubmit}
/>
</div>
);
}
}
and use it in your App:
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text'
name: 'firstName'
value: this.state.firstName,
onChange: this.handleValidation.bind(this)
}}
onSubmit={this.state.submit}
/>
<FormItem label='Phone number:' input={{
type:'tel'
name :'telNo'
value : {this.state.telNo}
onChange : {this.handleValidation.bind(this)}
}}
onSubmit={this.state.submit}
/>
<button>
Submit
</button>
</form>
)
}
this is where libraries like react-final-form and redux-form become handy.
UPD
ErrorOutput component should not validate anything, it is not a responsibility of a component. Instead, you could validate your values on inputs blur event and before submit:
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false,
errors: {},
invalid: false,
}
}
handleSubmit(e){
e.preventDefault()
if (this.validate()) {
// handle error
} else {
// submit
}
}
validate = () => {
const { firstName, telNo } = this.state
const errors = {}
let invalid = false;
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = 'first name is required'
invalid = true;
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
telNo.telNo = 'telNo is required'
invalid = true;
}
this.setState({
invalid,
errors,
})
return invalid;
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text',
name: 'firstName',
value: this.state.firstName,
onChange: e => this.setState({ firstName: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<FormItem label='Phone number:' input={{
type: 'tel',
name: 'telNo',
value: this.state.telNo,
onChange: e => this.setState({ telNo: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<button>
Submit
</button>
</form>
)
}
}
and FormItem and ErrorOutput:
const ErrorOutput = ({ error }) => <span>{error}</span>
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
{this.props.error && <ErrorOutput error={this.props.error} />}
</div>
);
}
}
const ErrorOutput = ({ errorText }) => <span>{errorText}</span>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
telNo: "",
submit: false,
errors: {} //Add errors object to the state.
};
}
handleSubmit(e) {
e.preventDefault();
const errors = this.validateData();
if (Object.keys(errors).length === 0) {
alert("Success");
}
//else errors exist
this.setState({ errors });
}
validateData = () => {
let errors = {};
const { firstName, telNo } = this.state; // read the values to validate
if (firstName.length === 0) {
errors.firstName = "Required";
} else if (firstName.length > 0 && !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = "Letters only";
}
if (telNo.length === 0) {
errors.telNo = "Required";
} else if (telNo.length > 0 && !telNo.match(/^[0-9]+$/)) {
errors.telNo = "Numbers only";
}
return errors;
};
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
const { errors } = this.state; // read errors from the state
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>First name:</label>
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleValidation.bind(this)}
/>
{errors.firstName && <ErrorOutput errorText={errors.firstName} />}
</div>
<div>
<label>Phone number:</label>
<input
type="tel"
name="telNo"
value={this.state.telNo}
onChange={this.handleValidation.bind(this)}
/>
{errors.telNo && <ErrorOutput errorText={errors.telNo} />}
</div>
<button>Submit</button>
</form>
);
}
}
render(<App />, document.getElementById("root"));

Form validation before the submission and at time of the data entry

I started to study React and wanted to create the form for multiple inputs, where I can check the validation of the data at the time of input and again before submitting of the form.
The conditions to submit: all fields are mandatory and the data is valid.
Currently, if user enters invalid data in input field, error text is displayed near the same field. And if user clicked button "submit" on the form with empty fields, error text is also displayed.
But I can't really work it out, how should I do the validation before the submission of the form in my example: : the form has the input field with an error or not.
import React from 'react'
import { render } from 'react-dom'
const ErrorOutput = props => {
let name = props.name
let inputValue = props.case
let submit = props.submit
console.log(props.submit)
if (name === 'firstName') {
if (!inputValue.match(/^[a-zA-Z]+$/) && inputValue.length > 0) {
return <span>Letters only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
if (name === 'telNo') {
if(!inputValue.match(/^[0-9]+$/) && inputValue.length > 0) {
return <span>Numbers only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false
}
}
handleSubmit(e){
e.preventDefault()
let submit = true
this.setState ({submit: submit})
// ... Validation
}
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>
First name:
</label>
<input
type='text'
name ='firstName'
value = {this.state.firstName}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.firstName} name={'firstName'} submit = {this.state.submit} />
</div>
<div>
<label>
Phone number:
</label>
<input
type='tel'
name ='telNo'
value = {this.state.telNo}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.telNo} name={'telNo'} submit = {this.state.submit} />
</div>
<button>
Submit
</button>
</form>
)
}
}
render(
<App />,
document.getElementById('root')
)
the following code is an example of adding data via form and form validation prior to the submit. More validations can be added.
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
age: '',
email: '',
errorName: '',
errorAge: '',
errroMail: '',
dataValue: false
};
this.getName = this.getName.bind(this);
this.getAge = this.getAge.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.postDatainDisplay = this.postDatainDisplay.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.name !== nextProps.name) {
this.setState({ dataValue: true });
}
}
postDatainDisplay(dataObj) {
this.props.postData(dataObj);
}
getName(event) {
const { name, age } = this.state;
this.setState({ errorName: '' });
this.setState({ name: event });
}
getAge(event) {
const { age } = this.state;
this.setState({ errorAge: '' });
this.setState({ age: event });
}
handleSubmit() {
const { name, age } = this.state;
//add more validation here
if (name === '') {
this.setState({ errorName: 'name cannot be blank', dataValue: false
});
} else if (age === '') {
this.setState({ errorAge: 'Age cannot be blank', dataValue: false });
} else
{ this.setState({ data: { name, age } }, () => {
this.props.sendData(this.state.data);
}
render() {
const { name, age } = this.props;
return (
<div className="container">
<form>
name:<input
type="text"
onChange={event => {
this.getName(event.target.value);
}}
/>
{this.state.errorName}
<br />
<br />
age:{' '}
<input
type="text"
onChange={event => {
this.getAge(event.target.value);
}}
/>
{this.state.errorAge}
<br />
<br />
<input type="button" onClick={this.handleSubmit} value="Submit"
/>
</form>
</div>
class App extends React.Component {
constructor(props){
super(props)
this.state = {
form:{
firstName: {
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
telNo: {
value: '',
validation: {
required: true
},
valid: false,
touched: false
}
},
formIsValid:false
}
}
checkValidity(value, rules) {
let isValid = true;
if (rules.required) {
isValid = value.trim() !== '' && isValid;
}
return isValid;
}
handleValidation = (event) => {
let fieldName = event.target.name;
let fieldValue = event.target.value;
const updatedCategoryForm = {
...this.state.form
};
const updatedFormElement = {
...updatedCategoryForm[fieldName]
};
updatedFormElement.touched = true;
updatedFormElement.value = fieldValue;
updatedFormElement.valid = this.checkValidity(updatedFormElement.value, updatedFormElement.validation);
if (!updatedFormElement.valid && updatedFormElement.validation ) {
updatedFormElement.elementValidation = "Invalid";
} else {
updatedFormElement.elementValidation = "";
}
updatedCategoryForm[fieldName] = updatedFormElement;
let formIsValid = true;
for (let inputIdentifier in updatedCategoryForm) {
formIsValid = updatedCategoryForm[inputIdentifier].valid && formIsValid;
}
this.setState({ form: updatedCategoryForm, formIsValid: true });
}
Based on the value of formIsValid field disable submit button

Load form value from state

First I show a list of transactions, when a user selects a single transaction a new page is opened with the transaction ID in the URL. On this page are details of the transaction displayed.
The code below is just the details page. It shows all the right details.
One of the details is a list of 0 or more tags, I'd like to be able to edit the list of tags and save the result.
At this point, I always end up with a clean Input field and I do not understand how to populate this field with the existing transaction['tags'] data.
It seems that the transaction['tags'] is not initialized until the page is rendered, I cannot use it in the constructor or in the componentDidMount.
What I expect is that the transaction object as stated in the mapStateToProps is available and I can change the line in the constructor from: this.state = {value: ''}; to this.state = {value: transaction['tags']}
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { fetchTransaction } from '../actions';
class TransactionsIndex extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
const { _key } = this.props.match.params;
this.props.fetchTransaction(_key);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
const { transaction } = this.props;
if (!transaction) {
return <div>Loading...</div>;
}
let tags = null;
tags =
<div>
<form onSubmit={this.handleSaveTagsclick}>
<input type="text" value={this.state.value} onChange={this.handleChange} />
<input type="submit" value="Submit" />
</form>
</div>
// console.log(transaction['tags']);
return (
<div className="container">
<div className="well">
<div>Transactiedatum: { transaction["Transactiedatum"] }</div>
<div>Valutacode: { transaction["Valutacode"] }</div>
<div>CreditDebet: { transaction["CreditDebet"] }</div>
<div>Bedrag: { transaction["Bedrag"] }</div>
<div>Tegenrekeningnummer: { transaction["Tegenrekeningnummer"] }</div>
<div>Tegenrekeninghouder: { transaction["Tegenrekeninghouder"] }</div>
<div>Valutadatum: { transaction["Valutadatum"] }</div>
<div>Betaalwijze: { transaction["Betaalwijze"] }</div>
<div>Omschrijving: { transaction["Omschrijving"] }</div>
<div>Type betaling: { transaction["Type betaling"] }</div>
<div>Machtigingsnummer: { transaction["Machtigingsnummer"] }</div>
<div>Incassant ID: { transaction["Incassant ID"] }</div>
<div>Adres: { transaction["Adres"] }</div>
<div>Status: { transaction["status"] }</div>
<div>Created: { transaction["created"] }</div>
{tags}
</div>
<Link to="/"><button type="button" className="btn btn-default">Back</button></Link>
</div>
);
};
}
function mapStateToProps({ transactions }) {
// console.log('transactions_selectedTransaction: ' + transactions['selectedTransaction']);
return { transaction: transactions['selectedTransaction'] };
}
export default connect(mapStateToProps, { fetchTransaction })(TransactionsIndex);
I found this but it did not help me: Redux-form: Set form values from state
and this: How to get state / value from form component?

On refresh react application I need to get from componentWillReceiveProps values for input text

I have the following problem and I really need help on that.
export class DeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillMount() {
let data = this.props.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
componentWillReceiveProps(newProps) {
let data = newProps.devices.data.find(device => device.id ===
`${deviceID}`) || {};
this.setState({ value: data.name })
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" defaultValue={this.state.value} onChange= .
{this.handleChange} />
</Form.Label>)
}
}
So what I want is that when I refresh the page, I want to get the the this.state.value on my input.. which in this case I am not able to do that. So I would like to know what I am doing wrong here. If I set it on value on the input I did get what I want, but then I have an warning like that:
A component is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
What can I do?
Actually, you should handle changes there and you can use just value on the input field instead of defaultValue.
For example:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: '',
// if it comes from props by default
// you can use, if not just leave as it is
value: props.value
};
}
handleChange = e => {
this.setState({value: e.target.value});
}
render() {
const { error } = this.props;
return (
<form>
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</form>
)
}
}
Hope it will helps.
So from what I understand you want to make controlled input but use props.value as a default value. What if you do:
export class AdminDeviceEdit extends React.PureComponent<Props> {
constructor(props) {
super(props);
this.state = {
value: props.value,
};
this.handleChange = this.handleChange.bind(this);
}
componentWillReceiveProps(newProps) {
if(this.props.value !== newProps.value) {
this.setState({ value: newProps.value }) // reset input value
}
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { error } = this.props;
return (
<FormLabel>Internal ID</FormLabel>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</Form.Label>)
}
}
Certainly get rid of componentWillMount and componentDidMount. You don't need them here.

Adding Validation to a Dynamically constructed form

Scenario
I am developing a savedialog which accepts any simple object with string, boolean and date fields. I've constructed a component which handels the construction of this, now I would like to dynamically add validation to the input fields which have been created.
I'm programming in TypeScript, using Angular2 and Also a framework called PrimeNG which is the Angular2 version of PrimeFaces.
My code at the moment
Html
<p-dialog #dialog header="{{dialogTitle}}" [(visible)]="isDisplayed" [responsive]="true" showEffect="fade"
[modal]="true" [closable]="false" [closeOnEscape]="false" [modal]="true" [width]="1000" [resizable]="false">
<form novalidate #form (ngSubmit)="save()">
<div class="ui-grid ui-grid-responsive ui-fluid" *ngIf="saveObject">
<div *ngFor="let i of rows" class="ui-grid-row">
<div class="ui-grid-col-6" *ngFor="let field of fields | slice:(i*itemsPerRow):(i+1)*itemsPerRow">
<div class="ui-grid-col-5 field-label">
<label for="attribute">{{field.key}}</label>
</div>
<div class="ui-grid-col-5">
<p-checkbox *ngIf="booleanFields.indexOf(field.key) !== -1" binary="true"
[(ngModel)]="saveObject[field.key]" name="{{field.key}}"
[ngClass]="{'error-border-class': field.key.errors && (field.key.dirty || field.key.touched)}"
[valueValidator]="field.key" [validateObject]="saveObject"></p-checkbox>
<p-calendar *ngIf="dateFields.indexOf(field.key) !== -1" [(ngModel)]="saveObject[field.key]"
name="{{field.key}}"
[ngClass]="{'error-border-class': field.key.errors && (field.key.dirty || field.key.touched)}"
dateFormat="dd/mm/yy" [showIcon]="true" [appendTo]="dialog"
[monthNavigator]="true"
[ngClass]="{'error-border-class': field.key.errors && (field.key.dirty || field.key.touched)}"
[valueValidator]="field.key"
[validateObject]="saveObject"></p-calendar>
<input *ngIf="(booleanFields.indexOf(field.key) === -1) && (dateFields.indexOf(field.key) === -1)"
pInputText id="attribute" [(ngModel)]="saveObject[field.key]" name="{{field.key}}"
[ngClass]="{'error-border-class': field.key.errors && (field.key.dirty || field.key.touched)}"
[valueValidator]="field.key" [validateObject]="saveObject"/>
</div>
</div>
</div>
</div>
</form>
<p-footer>
<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
<button label=" " type="button" pButton (click)="hideDialog(true)">
<i class="fa fa-times fa-fw" aria-hidden="true"></i>Sluit
</button>
<button label=" " type="submit" pButton (click)="hideDialog(false)" [disabled]="!form.valid">
<i class="fa fa-check fa-fw" aria-hidden="true"></i>Opslaan
</button>
</div>
</p-footer>
SaveDialogComponent
import { Component, Input, Output, OnChanges, EventEmitter } from '#angular/core';
import { FieldUtils } from '../utils/fieldUtils';
import { ValueListService } from '../value-list/value-list.service';
#Component({
moduleId: module.id,
selector: 'save-dialog',
templateUrl: 'savedialog.component.html',
styleUrls: ['savedialog.component.css']
})
export class SaveDialogComponent implements OnChanges {
#Input() dialogTitle:string;
#Input() isDisplayed:boolean;
#Input() saveObject:any;
#Input() unwantedFields:string[] = [];
#Input() booleanFields:string[] = [];
#Input() dateFields:string[] = [];
#Output() hide:EventEmitter<boolean> = new EventEmitter<boolean>();
#Output() result:EventEmitter<Object> = new EventEmitter<any>();
private fields:any[];
private itemsPerRow:number = 2;
private rows:any[];
ngOnChanges() {
this.fields = FieldUtils.getFieldsWithValues(this.saveObject, this.unwantedFields);
this.rows = Array.from(Array(Math.ceil(this.fields.length / this.itemsPerRow)).keys());
}
hideDialog(clearChanges:boolean) {
if(clearChanges){
this.resetObject()
}
this.isDisplayed = false;
this.hide.emit(this.isDisplayed);
}
save() {
this.result.emit(this.saveObject);
}
private resetObject() {
for(let field of this.fields) {
this.saveObject[field.key] = field.value;
}
}
private getDate(date:string):Date {
return new Date(date);
}
}
I'm Not including field utils since this it isn't that important, basically It extracts all the fields from an object, uses the booleanFields and dateFields to order the fields inorder to place all the fields of a same type next to one another.
ValidatorDirective
import { Directive, Input } from '#angular/core';
import { AbstractControl, ValidatorFn, Validator, NG_VALIDATORS, FormControl } from '#angular/forms';
#Directive({
selector: '[valueValidator][ngModel]',
providers: [
{provide: NG_VALIDATORS, useExisting: ValueValidatorDirective, multi: true}
]
})
export class ValueValidatorDirective implements Validator {
#Input('valueValidator') fieldName:string;
#Input() validateObject:any;
#Input() values:any[];
datumStartValidator:ValidatorFn;
datumEindValidator:ValidatorFn;
codeValidator:ValidatorFn;
naamValidator:ValidatorFn;
volgNrValidator:ValidatorFn;
validate(c:FormControl) {
this.instantiateFields();
switch (this.fieldName) {
case 'datumStart':
return this.datumStartValidator(c);
case 'datumEind':
return this.datumEindValidator(c);
case 'code':
return this.codeValidator(c);
case 'naam':
return this.naamValidator(c);
case 'volgnr':
return this.volgNrValidator(c);
default :
return {
error: {
valid: false
}
}
}
}
instantiateFields() {
this.datumStartValidator = validateStartDatum(this.validateObject['datumEind']);
this.datumEindValidator = validateEindDatum(this.validateObject['datumStart']);
this.codeValidator = validateCode();
this.naamValidator = validateNaam();
this.volgNrValidator = validateVolgNr(null);
}
}
//We'll need multiple validator-functions here. one for each field.
function validateStartDatum(datumEind:Date):ValidatorFn {
return (c:AbstractControl) => {
let startDatum:Date = c.value;
let isNotNull:boolean = startDatum !== null;
let isBeforeEind:boolean = false;
if (isNotNull) {
isBeforeEind = datumEind.getTime() > startDatum.getTime();
}
if (isNotNull && isBeforeEind) {
return null
} else {
return returnError();
}
}
}
function validateEindDatum(startDatum:Date):ValidatorFn {
return (c:AbstractControl) => {
let eindDatum:Date = c.value;
let isNotNull:boolean = eindDatum !== null;
let isBeforeEind:boolean = false;
if (isNotNull) {
isBeforeEind = eindDatum.getTime() > startDatum.getTime();
}
if (isNotNull && isBeforeEind) {
return null
} else {
return returnError();
}
}
}
function validateCode():ValidatorFn {
return (c:AbstractControl) => {
let code:string = c.value;
if (code !== null) {
return null;
} else {
return returnError();
}
}
}
function validateNaam():ValidatorFn {
return (c:AbstractControl) => {
let naam:string = c.value;
if (naam !== null) {
return null;
} else {
return returnError();
}
}
}
function validateVolgNr(volgnummers:string[]):ValidatorFn {
return (c:AbstractControl) => {
return returnError();
}
}
function returnError():any {
return {
error: {
valid: false
}
}
}
My problem
First of all I want to say thank you for reading this all the way, you're a champ! I know my code is not the most well writen beautiful code you've ever seen but I'm just trying to get it to work at the moment. The problem I have at the moment is that Validation happens but I can't seem to set the class using
[ngClass]="{'error-border-class': field.key.errors && (field.key.dirty || field.key.touched)}"
field.key is found no problem But I don't seem to be able to use that as reference to the dynamic name given to the input fields. field.key.errors is undefined which I guess is to be expected. But I haven't found how to fix this yet.
Secondly I think that the way I have It now I'm going to end up having trouble with the validation of the entire form (to disable the submit button)
<button label=" " type="submit" pButton (click)="hideDialog(false)" [disabled]="!form.valid">
Any Ideas on how to fix this are welcome, I'm open to all suggestions.