Please Refer to my plunkr
I've been playing around with the new Angular 2 RC and I think I have figured out how the form validation works.
First I build 2 objects called defaultValidationMessages and formDefinition
private defaultValidationMessages: { [id: string]: string };
formDefinition: {
[fieldname: string]:
{
displayName: string,
placeholder: string,
currentErrorMessage: string,
customValidationMessages: { [errorKey: string]: string }
defaultValidators: ValidatorFn,
defaultValue: any
}
};
Then I load up those objects with the default validators and field information. and build the ControlGroup from the formDefinition object.
this.defaultValidationMessages = {
'required': '{displayName} is required',
'minlength': '{displayName} must be at least {minlength} characters',
'maxlength': '{displayName} cannot exceed {maxlength} characters',
'pattern': '{displayName} is not valid'
}
this.formDefinition = {
'name': {
displayName: 'Name',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose(
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(50)
]),
defaultValue: this.person.name
},
'isEmployee': {
displayName: 'Is Employee',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: {},
defaultValidators: Validators.compose([]),
defaultValue: this.person.isEmployee
},
'employeeId': {
displayName: 'Employee Id',
placeholder: '',
currentErrorMessage: '',
customValidationMessages: { 'pattern': '{displayName} must be 5 numerical digits' },
defaultValidators: Validators.compose(
[
Validators.pattern((/\d{5}/).source)
]),
defaultValue: this.person.employeeId
}
}
this.personForm = this.formBuilder.group({});
for (var v in this.formDefinition) {
this.personForm.addControl(v, new Control(this.formDefinition[v].defaultValue, this.formDefinition[v].defaultValidators));
}
this.personForm.valueChanges
.map(value => {
return value;
})
.subscribe(data => this.onValueChanged(data));
Using a technique that I learned from Deborah Kurata's ng-conf 2016 session I bind a method to the ControlGroups valueChanges event.
By defining sets of default validators on each control it allows the control to dynamically append new validators to it based on future action. And then clearing back to the default validators later.
Issue I still have.
I was having an issue getting my typescript intellisense to import the ValidatorFn type. I found it here but I don't think I'm suppose to access it like this:
import { ValidatorFn } from '../../../node_modules/#angular/common/src/forms/directives/validators'
I also had to reset the form by setting some internal members. Is there a better way to reset the form? see below:
(<any> this.programForm.controls[v])._touched = false;
(<any> this.programForm.controls[v])._dirty = false;
(<any> this.programForm.controls[v])._pristine = true;
Please look at my plunk and let me know if there is a better way to handle model driven dynamic form validation?
My import string looks like this and it isn't marked as an error.
import { ValidatorFn } from '#angular/common/src/forms/directives/validators';
And some info about the reset form issue. There is not proper reset feature available yet, but a workaround exists. I've found it in docs.
You need a component field
active: true;
and you need to check it in your form tag:
<form *ngIf="active">
After that you should change your personFormSubmit() method to:
personFormSubmit() {
this.person = new Person();
this.active = false;
setTimeout(()=> {
this.active=true;
this.changeDetectorRef.detectChanges();
alert("Form submitted and reset.");
}, 0);
}
I tried this solution with you plnkr example and seems that it works.
Related
Using class-validator with Nest.js. I want to validate these two cases:
Validate the input date is older than now, then give a message: Date can't before than now.
#Field(() => Date, { description: 'Due Date' })
dueDate: Date;
Validate if all of the keys are unique in an array. But this way only can check if the ID is uuid. Is it possible to check if the IDs are the same in the array? Ex: ['1234-1234-1234-1234', '1234-1234-1234-1234']
#Field(() => [String], { description: 'product IDs' })
#IsUUID('all', { each: true, message: 'Product ID is not valid.' })
productIds: string[];
I searched and couldn't find a suitable inheriting validation decorators. You can custom validation classes like this:
#ValidatorConstraint()
export class IsAfterNowConstraint implements ValidatorConstraintInterface {
validate(date: Date) {
return Date.now() < date.getTime();
}
defaultMessage(args: ValidationArguments) {
return `Date ${args.property} can not before now.`;
}
}
function IsAfterNow(validationOptions?: ValidationOptions) {
// eslint-disable-next-line #typescript-eslint/ban-types
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: IsAfterNowConstraint,
});
};
}
#ArrayUnique(identifier?: (o) => any): Checks if all array's values are unique. Comparison for objects is reference-based. Optional function can be speciefied which return value will be used for the comparsion.
You can compare two dates with this function
import { ValidateBy, ValidationOptions, buildMessage, ValidationArguments } from 'class-validator'
export const IsAfter = (property: string, options?: ValidationOptions): PropertyDecorator =>
ValidateBy(
{
name: 'IsAfter',
constraints: [property],
validator: {
validate: (value: Date, args: ValidationArguments): boolean => {
const [relatedPropertyName] = args.constraints
const relatedValue = (args.object as Record<string, unknown>)[relatedPropertyName] as Date
return value.toISOString() > relatedValue.toISOString()
},
defaultMessage: buildMessage((each: string): string => each + '$property must be after $constraint1', options),
},
},
options,
)
in this kind of situations
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly from!: Date
#IsAfter('from')
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly to!: Date
I'm trying to set up a validation schema to use with Formik. I have a combobox that has items of shape { id: number; value: string; } and pulls the value out to display to the user, while submitting the whole item to Formik/Yup. However, in production, I won't know the shape of my items ahead of time; this is just the shape I've chosen for demoing.
const items = [
{ id: 1, value: 'foo' },
{ id: 2, value: 'bar' },
];
const [ field, meta, helpers ] = useField('value');
return (
<ComboBox
{...field}
invalid={meta.touched && !!meta.errors}
invalidText={meta.errors}
items={items}
itemToString={i => i?.value ?? ''}
onChange={data => helpers.setValue(data.selectedItem)}
selectedItem={field.value}
/>
);
I want to make only id = 1 to be valid.
const validationSchema = Yup.object({
value: Yup.object({
id: Yup.number().oneOf([1], "You've selected an invalid option."),
value: Yup.string(),
})
.required('You have not selected an option.')
.nullable(),
});
However, when this is in the error state, meta.errors is set to { id: "You've selected an invalid option." }. invalidText expects a ReactChild so when it receives this object, React throws an error.
I assume the solution, then, is to move .oneOf() to outside of the inner Yup.object(). I don't know how to specify the valid values, however, and neither the documentation nor a quick search of SO helped. Thoughts?
react-select just upgraded to 2.0.0 so google results on the first three pages are all about older versions, even the official document, and none of them helped.
My select box can show all options correctly, but redux form won't pick up the value, with the warning: Warning: A component is changing a controlled input of type hidden to be uncontrolled.
I wonder what have I missed here...
Form component:
<Field
name="residentialAddress"
label = "Residential Address"
type="select"
component={AddressField}
validate={required}
/>
Component
export class AddressField extends Component {
searchAddress = input => {
let options = []
return myPromise(input)
.then(suggestions => {
options = suggestions.map(suggestion =>
({
label: suggestion.label,
data: suggestion.value
})
)
return options;
}
).catch(
error => {
return options = [{ label: "Auto fetching failed, please enter your address manually", value: "", isDisabled: true }];
}
);
};
render() {
const {
input,
label,
meta: { touched, error },
type
} = this.props;
return(
<FormGroup>
<ControlLabel>{label}</ControlLabel>
<Async
{...input}
placeholder={label}
isClearable={true}
getOptionValue={(option) => option.residentialAddress}
onChange = { value => input.onChange(value.data) }
loadOptions={this.searchAddress}
/>
{ touched && error && <span>{error}</span> }
</FormGroup>
)
}
Solution: Simply remove the {...input} in <Async>.
Unlike regular custom Field component where we need to pass in {input}, the react-select Async component seems to take care of itself very well and doesn't require any intervene. Someone may explain it in a more professional way perhaps...
Also worth mention for those who come across this question:
loadOptions with promise used to require object {options: options} as return type. Now it changes to just array (options as in my code). But I didn't find any document that mentions this one.
Hope this could help.
is there a way to call an attribute from the same model? Because I want to use an attribute, from model/code.js, to calculate the validator of an other attribute from the same file. I'll show you with example.
//model/code.js
import Ember from 'ember';
import DS from 'ember-data';
import {validator, buildValidations} from 'ember-cp-validations';
const CardValidations = buildValidations(
{
cardId: {
validators: [
validator('presence', true),
validator('length', {
// here instead of 10, I want to use nbBits
max: 10
}
]
}
}
);
export default Credential.extend(CardValidations, {
cardId: DS.attr('string'),
nbBits: DS.attr('number'),
displayIdentifier: Ember.computed.alias('cardId'),
});
So as you can see, I want to call nbBits, to have a specific validation for cardId.
Does somebody know the methods or give me a tips? Thank you for your time
Your case is described in the official documentation of ember-cp-validations as follows:
const Validations = buildValidations({
username: validator('length', {
disabled: Ember.computed.not('model.meta.username.isEnabled'),
min: Ember.computed.readOnly('model.meta.username.minLength'),
max: Ember.computed.readOnly('model.meta.username.maxLength'),
description: Ember.computed(function() {
// CPs have access to the model and attribute
return this.get('model').generateDescription(this.get('attribute'));
}).volatile() // Disable caching and force recompute on every get call
})
});
Your yet simpler case would look like this:
const CardValidations = buildValidations(
{
cardId: {
validators: [
validator('presence', true),
validator('length', {
// here instead of 10, I want to use nbBits
max: Ember.computed.readOnly('model.nbBits')
}
]
}
}
);
I'm trying to validate fields in my form, but I keep getting an error message.
Here is my code:
Ext.define('ExtDoc.views.extfields.FieldsValidator',{
valEng: function(val) {
var engTest = /^[a-zA-Z0-9\s]+$/;
Ext.apply(Ext.form.field.VTypes, {
eng: function(val, field) {
return engTest.test(val);
},
engText: 'Write it in English Please',
// vtype Mask property: The keystroke filter mask
engMask: /[a-zA-Z0-9_\u0600-\u06FF\s]/i
});
}
});
And I define my field as follow:
{
"name": "tik_moed_chasifa",
"type": "ExtDoc.views.extfields.ExtDocTextField",
"label": "moed_hasifa",
"vtype": "eng",
"msgTarget": "under"
}
The first snippet is in a separate js file, and I have it in my fields js file as required.
When I start typing text in the text field, I keep seeing the following error msg in the explorer debugger:
"SCRIPT438: Object doesn't support property or method 'eng' "
What could it be? Have I declared something wrong?
You have defined your own class with a function valEng(val), but you don't instantiate it, neither do you call the function anywhere.
Furthermore, your function valEng(val) does not require a parameter, because you are not using that parameter anywhere.
It would be far easier and more readable, would you remove the Ext.define part and create the validators right where you need them. For instance if you need them inside an initComponent function:
initComponent:function() {
var me = this;
Ext.apply(Ext.form.field.VTypes, {
mobileNumber:function(val, field) {
var numeric = /^[0-9]+$/
if(!Ext.String.startsWith(val,'+')) return false;
if(!numeric.test(val.substring(1))) return false;
return true;
},
mobileNumberText:'This is not a valid mobile number'
});
Ext.apply(me,{
....
items: [{
xtype:'fieldcontainer',
items:[{
xtype: 'combobox',
vtype: 'mobileNumber',
Or, you could add to your Application.js, in the init method, if you need it quite often at different levels of your application:
Ext.define('MyApp.Application', {
extend: 'Ext.app.Application',
views: [
],
controllers: [
],
stores: [
],
init:function() {
Ext.apply(Ext.form.field.VTypes, {
mobileNumber:function(val, field) {
var numeric = /^[0-9]+$/
if(!Ext.String.startsWith(val,'+')) return false;
if(!numeric.test(val.substring(1))) return false;
return true;
},
mobileNumberText:'This is not a valid mobile number'
});
}