I have such custom validator:
static isEmail(control:Control):{[key:string]:boolean} {
let emailRegExp = new RegExp("^[-a-z0-9~!$%^&*_=+}{'?]+(.[-a-z0-9~!$%^&*_=+}{'?]+)*#([a-z0-9_][-a-z0-9_]*(.[-a-z0-9_]+)*.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}))(:[0-9]{1,5})?$", 'i');
if (control.value.match(emailRegExp)) {
return {isEmail: true};
}
return {isEmail: false};
}
Use it in component:
loginForm:ControlGroup;
constructor(private _router:Router,
private _loginService:LoginService,
private _formBuilder:FormBuilder) {
this.loginForm = _formBuilder.group({
email: ['', Validators.compose([
Validators.required,
Validators.minLength(8),
FormValidationService.isEmail
])],
password: ['', Validators.compose([
Validators.required,
Validators.minLength(8),
Validators.maxLength(30),
FormValidationService.isPassword
])]
});
}
And template:
<form id="login-form" role="form" [ngFormModel]="loginForm" (ngSubmit)="onLoginSubmit()">
<div class="form-group">
<label class="control-label" for="loginFormEmail">Email</label>
<input type="text" id="loginFormEmail" class="form-control"
ngControl="email" #email="ngForm">
<div *ngIf="email.dirty && !email.valid">
<div class="alert alert-danger" role="alert" [hidden]="!email.errors.minlength">
Email field must have more then 8 characters
</div>
<div class="alert alert-danger" role="alert" [hidden]="email.errors.isEmail">
Email not valid
</div>
</div>
</div>
<div class="form-group">
<label class="control-label" for="loginFormPassword">Password</label>
<input type="password" id="loginFormPassword" class="form-control"
ngControl="password" #password="ngForm">
<div *ngIf="password.dirty && !password.valid">
<div class="alert alert-danger" role="alert" [hidden]="!password.errors.minlength">
Password field must have more then 8 characters
</div>
<div class="alert alert-danger" role="alert" [hidden]="!password.errors.maxlength">
Password field must have no more then 30 characters
</div>
<div class="alert alert-danger" role="alert" [hidden]="password.errors.isPassword">
Password must meet the following rules:
<ul>
<li>At least one upper case english letter</li>
<li>At least one lower case english letter</li>
<li>At least one digit</li>
<li>At least one special character</li>
</ul>
</div>
</div>
</div>
<p>If you forgot your password you can <a (click)="toForgot()">reset it</a>.</p>
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block" [disabled]="!loginForm.valid">Login</button>
</div>
</form>
If you look in screenshot you can see that custom validator return my value. But regardless of its value control.valid always false, so form invalid to.
I try to set control.valid = true, but this field have only getter.
I found answer. When control value is valid you must return null in your custom validator:
static isPassword(control:Control):{[key:string]:boolean} {
let passwordRegExp = new RegExp("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{8,}$");
if (control.value.match(passwordRegExp)) {
return null;
}
return {isPassword: false};
}
I think you need to add ngControlGroup to wrapping elements otherwise the form won't track the containing input elements
<div class="form-group" ngControlGroup="someName">
See also NgControlGroup
You mixed inline form definition with ngForm and definition in JS code using the FormBuilder class.
You should refactor your input definitions this way to use the ngFormControl directive:
<input [ngFomControl]="loginForm.controls.password" .../>
Related
I have made a search products app where I have form 3 fields from which I want to search the products. On submission the searchProducts() function is called which sends a get request to my api with request parameters it got from the form fields.
Problem - My form works only once. I can search only once and then I have to refresh the page to search again. Sometimes it works twice but not more than that. I do not know why.
I want to keep calling the searchProducts() function whenever the form is submitted. I want to work it again and again without refreshing the page as a user can search multiple times.
My searchProducts() function / Component -
export class SearchproductComponent implements OnInit {
isAuthenticated(){
return this.userService.isAuthenticated();
}
productSearchForm = this.fb.group({
productCode: [, []],
name: [, []],
brand: [, []],
priceLow: [,[]],
priceHigh: [, []]
})
get productCode(){
return this.productSearchForm.get('productCode');
}
get name(){
return this.productSearchForm.get('name');
}
get brand(){
return this.productSearchForm.get('brand');
}
constructor(private fb: FormBuilder, public productsService: ProductsService, public userService: UserserviceService) { }
ngOnInit(): void {
}
searchProducts(){
let name = this.productSearchForm.controls.name.value!
let brand = this.productSearchForm.controls.brand.value!
let productCode = this.productSearchForm.controls.productCode.value!
if (name && brand === null && productCode === null){
let response = this.productsService.getProductsByName(name)
console.log(response)
}
if (name && brand && productCode === null){
let response = this.productsService.getProductsByNameAndBrand(name, brand)
console.log(response)
console.log("Name and Brand Working");
}
if (name && brand && productCode){
let response = this.productsService.getProductsByNameAndBrandAndProductCode(name, brand, productCode)
console.log(response)
console.log("Name and Brand and Product Code Working")
}
It has more functions but you get the idea.
My searchProducts html file -
<div class="search-form">
<form (ngSubmit)="searchProducts()" [formGroup]="productSearchForm" name="productSearch" class="form-inline">
<div class="row">
<div class="col-4">
<div class="mb-3">
<label for="product-code" class="form-label">Product Code: </label>
<input type="text" class="form-control" id="product-code" name="product-code" formControlName="productCode">
</div>
</div>
<div class="col-4">
<div class="mb-3">
<label for="name" class="form-label">Name: </label>
<input type="text" class="form-control" id="name" name="name" formControlName="name">
</div>
</div>
<div class="col-4">
<div class="mb-3">
<label for="brand" class="form-label">Brand: </label>
<input type="text" class="form-control" id="brand" name="brand" formControlName="brand">
</div></div>
</div>
<div class="row">
<div class="col-2 mb-3">
<button type="submit" class="btn btn-primary mb-3">Search</button>
<button type="button" class="btn btn-warning" (click)="filterAnother()">Search Another</button>
</div>
<div class="col-10 mb-3" *ngIf="isAuthenticated();">
<div class="row">
<div class="col-2">
</div>
<div class="col-2">
<button type="button" class="btn btn-success mb-3" (click)="filterByPrice()">Filter by Price</button>
<button type="button" class="btn btn-primary" (click)="filterAnother()">Filter Another</button>
</div>
<div class="col-2 mu-2">
<label for="price-range" class="form-label price-range-label">Price Range: </label>
</div>
<div class="col-3">
<input type="text" class="form-control" id="price-range-low" name="price-low" formControlName="priceLow">
</div>
<div class="col-3">
<input type="text" class="form-control" id="price-range-high" name="price-high" formControlName="priceHigh">
</div>
</div>
</div>
</div>
</form>
</div>
I want searchProducts() to work again and again. It only works once.
My UI looks like this -
I tried adding another button search another to run function onclick but still does not work.
In your first if function, you are checking if all your inputs are null:
if (name && brand === null && productCode === null)
UPDATE:
You can do the following instead since you only search when there is a name typed in:
if (name !== null && brand === null && productCode === null)
So if you only search nulls, you won't get a response from your service I imagine. You're also doing the same check in the second if, just with a different logic:
if (name && brand && productCode === null)
UPDATE:
You can do the following instead since you are searching whenever there is a name & brand typed in:
if (name !== null && brand !== null && productCode === null)
Your third if function works since you are making sure that all of them exist, with this logic you should be able to search, but only if all 3 inputs have values, you can try in the link below:
if (name && brand && productCode)
I reproduced your code in StackBlitz and it looks like you need to improve your first 2 if statements logic so you can properly search when only one value (or 2) is being passed to angular service. Here is the StackBlitz link if you want to review it: https://stackblitz.com/edit/angular-ivy-ywlaqz?file=src/app/app.component.ts.
I would like to show the backend error messages on my Vue component, so for doing that I have created this component:
<template>
<section class="p-0 d-flex align-items-center">
<div class="container-fluid">
<div class="row">
<!-- Right START -->
<div class="col-md-12 col-lg-8 col-xl-9 mx-auto my-5 position-relative">
<!-- Shape Decoration END -->
<div class="row h-100">
<div
class="
col-md-12 col-lg-10 col-xl-5
text-start
mx-auto
d-flex
align-items-center
"
>
<div class="w-100">
<h3>Sign up for your account!</h3>
<p>
Join us today! Create your account easily with less
information.
</p>
<!-- Form START -->
<form class="mt-4" #submit.prevent="submit">
<!-- Email -->
<div class="mb-3">
<label class="form-label" for="email">Email address</label>
<input
v-model="data.name"
v-bind:class="{ 'is-invalid': validate.email }"
required
type="email"
class="form-control"
id="email"
aria-describedby="emailHelp"
placeholder="E-mail"
/>
<div class="invalid-feedback">
{{ validate.email }}
</div>
<small id="emailHelp" class="form-text text-muted"
>We'll never share your email with anyone else.</small
>
</div>
<!-- Username -->
<div class="mb-3">
<label class="form-label" for="email">Username</label>
<input
v-model="data.username"
v-bind:class="{ 'is-invalid': validate.username }"
required
type="text"
class="form-control"
id="username"
placeholder="Username"
/>
<div class="invalid-feedback">
{{ validate.username }}
</div>
</div>
<!-- Password -->
<div class="mb-3">
<label class="form-label" for="password">Password</label>
<input
v-model="data.password"
v-bind:class="{ 'is-invalid': validate.password }"
required
type="password"
class="form-control"
id="password"
placeholder="*********"
/>
<div class="invalid-feedback">
{{ validate.password }}
</div>
</div>
<!-- Password -->
<div class="mb-3">
<label class="form-label" for="password2"
>Confirm Password</label
>
<input
v-model="data.password2"
v-bind:class="{ 'is-invalid': validate.password2 }"
type="password"
class="form-control"
id="password2"
placeholder="*********"
/>
<div class="invalid-feedback">
{{ validate.password2 }}
</div>
</div>
<!-- Checkbox -->
<div class="mb-3 form-check">
<input
type="checkbox"
class="form-check-input"
id="remember"
/>
<label class="form-check-label" for="remember"
>keep me signed in</label
>
</div>
<!-- Button -->
<div class="row align-items-center">
<div class="col-sm-4">
<button type="submit" class="btn btn-dark btn-line">
Sign me up
</button>
</div>
<div class="col-sm-8 text-sm-end">
<span class="text-muted"
>Already have an account?
Signin here</span
>
</div>
</div>
</form>
<!-- Form END -->
<div class="bg-dark-overlay-dotted py-2 my-4"></div>
</div>
</div>
</div>
</div>
<!-- Right END -->
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
import { useRouter } from "vue-router";
export default defineComponent({
name: "Register",
setup(props) {
const data = reactive({
username: "",
email: "",
password: "",
password2: "",
});
const validate = reactive({
email: "",
username: "",
password: "",
password2: "",
});
const router = useRouter();
const submit = async () => {
const res = await fetch(`${process.env.VUE_APP_API_URL}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (!res.ok) {
const errors = await res.json().then((response) => response.errors);
for (const err of errors) {
const param: string = err.param ?? "";
validate[param] = err.msg;
}
}
};
return {
data,
validate,
submit,
};
},
});
</script>
as you can see I have defined and also exposed the validate property, which contains all the fields of the form.
When the API call is executed on the form submit, I reiceve this response if the backend validation fails:
{
"errors": [
{
"value": "",
"msg": "username must be at least 4 characters long",
"param": "username",
"location": "body"
},
{
"value": "test#",
"msg": "password confirm is different",
"param": "password2",
"location": "body"
}
]
}
I binded the validate property to each input field, so if the value entered in a specific field is incorrect, an error will be appended near the field and also the is-invalid class of Bootstrap is applied.
The errors variable contains the response above, what I'm trying to do is assign to each property of validate (which are the same name of the fields), the error messages, and I did:
const param: string = err.param ?? "";
validate[param] = err.msg;
the problem's that I get:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ email: string; username: string; password: string; password2: string; }'.
No index signature with a parameter of type 'string' was found on type '{ email: string; username: string; password: string; password2: string; }'.
How can I fix this situation? And also, is there a better way to handle such scenario? 'cause I'm new to Vue and most probably I'm overcomplicating the situation here.
Kind regards
If you decided to add TypeScript to your project, it would be a good idea to start using it as intended, so let's firstly make a type for your error object:
type MyTypedError = {
value: string,
msg: string,
// the question mark indicates that the "param" property may not exists
// "typeof" infers the type of the "validate" variable (object in your case)
// "keyof" grabs all the property names from "validate":
param?: keyof typeof validate,
location: string
}
Let's make use of it. Now the TypeScript compiler will know the type of the received data.
if (!res.ok) {
// specify the type of the "errors" variable:
// MyTypedError[] (array of MyTypedError objects)
const errors: MyTypedError[] = await res.json().then((response) => response.errors);
...
}
The problem with your code: you are only guaranteeing that the param variable is type of string, but you don't promise the compiler that param holds any of the property names from validate. But since the compiler knows the type of err, it will even guide you how to make a working loop:
for (const err of errors) {
// We stated that 'param' property might not exist
if (err.param !== undefined) validate[err.param] = err.msg
}
EDIT: The assumption that err.param can also be undefined came from your code: err.param ?? "", though I don't see any reason why would it be.
I have three components: GalleryAddComponent to add a new element, GalleryItemComponent, to edit an element, FieldsComponent, the form I want to use in the components: GalleryAddComponent and GalleryItemComponent. All components are inside the GalleryComponent. But when I go to the component GalleryAddComponent to add a new element I get the error: ERROR TypeError: Cannot read property 'controls' of undefined. Also in the component: GalleryItemComponent.
Help solve this problem so that the editing and adding logic works correctly.
template of GalleryAddComponent
<div class="card">
<div class="card-body">
<form [formGroup]="angForm" novalidate>
<app-fields [formGroup]="angForm"></app-fields>
<div class="form-group but-group">
<button (click)="addPost(title.value, url.value); angForm.reset(title.value, url.value)"
[disabled]="angForm.pristine || angForm.invalid"
class="btn btn-primary">Add
</button>
<a routerLink="/" class="btn btn-danger">Back</a>
</div>
</form>
</div>
</div>
code of GalleryAddComponent
export class GalleryAddComponent implements OnInit {
angForm: FormGroup;
isAdded: boolean = false;
constructor(private fb: FormBuilder, private galleryService: GalleryService) {}
ngOnInit() {
this.angForm = this.fb.group({
title: ['', Validators.required],
url: ['', Validators.required]
});
}
addPost(title: string, url: string): void {
this.galleryService.add(title, url).subscribe(res => {
this.isAdded = true;
});
}
}
template of GalleryItemComponent
<div class="card" *ngIf="toggleEdit">
<h4>Edit your post</h4>
<div class="card-body">
<form [formGroup]="angForm" novalidate>
<app-fields [formGroup]="angForm"></app-fields>
<div class="form-group but-group">
<input type="button"
(click)="updatePost(title.value, url.value)"
[disabled]=" angForm.invalid"
class="btn btn-primary" value="Update Post">
</div>
</form>
</div>
</div>
code of GalleryItemComponent
export class GalleryItemComponent implements OnInit {
pic: Picture;
angForm: FormGroup;
constructor(private route: ActivatedRoute,
private galleryService: GalleryService, private fb: FormBuilder) {}
ngOnInit() {
this.angForm = this.fb.group({
title: ['', Validators.required],
url: ['', Validators.required]
});
this.showPost();
}
showPost(): void {
this.route.params.subscribe(params => {
this.galleryService.getPicture(params['id']).subscribe(res => {
this.pic = res;
this.angForm.setValue({title: res.title, url: res.url})
})
})
}
updatePost(title: string, url: string): void {
this.route.params.subscribe(params => {
this.galleryService.update(title, url, params['id']).subscribe(res => {
if (res.id === this.pic.id) {
this.pic.title = title;
this.pic.url = url;
}
});
});
}
}
template of FieldsComponent
<div [formGroup]="formGroup">
<div class="form-group">
<label class="col-md-4">Picture Title</label>
<input type="text" class="form-control" formControlName="title" minlength="1" #title/>
</div>
<div *ngIf="angForm.controls['title'].invalid && (angForm.controls['title'].dirty || angForm.controls['title'].touched)"
class="alert alert-danger">
<div *ngIf="angForm.controls['title'].errors.required">
Title is required.
</div>
</div>
<div class="form-group">
<label class="col-md-4">Picture Address (url)</label>
<input type="url" class="form-control" formControlName="url" #url pattern="https?://.+"
title="Include http://"/>
</div>
<div *ngIf="angForm.controls['url'].invalid && (angForm.controls['url'].dirty || angForm.controls['url'].touched)"
class="alert alert-danger">
Address(url) is required.
<div *ngIf="angForm.controls['url'].errors.required ">
</div>
</div>
</div>
code of FieldsComponent
export class FieldsComponent implements OnInit {
#Input() formGroup: FormGroup;
constructor() {}
ngOnInit() {}
}
You are getting this error because you are referencing angForms.controls in your FieldsComponent which only has one variable: formGroup.
Simply replace angForms in the template HTML code with formGroup and the issue should be resolved.
<div [formGroup]="formGroup">
<div class="form-group">
<label class="col-md-4">Picture Title</label>
<input type="text" class="form-control" formControlName="title" minlength="1" #title />
</div>
<div *ngIf="formGroup.controls['title'].invalid && (formGroup.controls['title'].dirty || formGroup.controls['title'].touched)"
class="alert alert-danger">
<div *ngIf="formGroup.controls['title'].errors.required">
Title is required.
</div>
</div>
<div class="form-group">
<label class="col-md-4">Picture Address (url)</label>
<input type="url" class="form-control" formControlName="url" #url pattern="https?://.+" title="Include http://" />
</div>
<div *ngIf="formGroup.controls['url'].invalid && (formGroup.controls['url'].dirty || formGroup.controls['url'].touched)"
class="alert alert-danger">
Address(url) is required.
<div *ngIf="formGroup.controls['url'].errors.required ">
</div>
</div>
</div>
I think the easy solution here, is to set up the formgroup in the parent component, and pass it on to the child components as input, hence it is given to the child component before the child component html is loaded.
The other solution I would recommend is to use the Resolve Technique offered by Angular, this allows you to load all data before a component is loaded with a quite straight-forward setup.
Here are some references:
https://www.techiediaries.com/angular-router-resolve/
https://alligator.io/angular/route-resolvers/
Cannot read property 'controls' of undefined. This error can come only coz instead of formGroup you have used angForm. Try replacing it as shared by #Wrokar. Also what I understand is you want to show consolidated error messages. So instead of doing it in html for each formcontrol, you should do it in component.ts and make it more generic, by subscribing to value change of each control like below, and show the consolidated error message.
for (const field in this.formGroup.controls) { // 'field' is a string
const control = this.form.get(field); // 'control' is a FormControl
control.valueChanges.subscribe(
(res) => {
// check if it is pristine, dirty, invalid, touched, pattern match if
any
// you can access control.errors
// create the consolidated list and append it in the list of error
messages
}
)
}
Its better to make it generic coz, tomorrow your form can have additional fields and then you dont have to change you FieldsComponent.
when clicked on submit button, it will call function, in that function i am trying to write logic to disable submit button when fields are not valid, here email must be contain #, dot and after dot minimum 2 & maximum 4 alphabet characters. I tried bellow code.
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<form name="myForm">
<div>
<select id="country" style="width:250px;" class="" name="selectFranchise" ng-model="state1" ng-change="displayState(state1)"
ng-required>
<option ng-repeat="(key,country) in countries" value="{{key}}">{{country[0]}}</option>
</select>
</div>
<div>
<select id="state" ng-disabled="!states[state1].length" ng-model="cities" ng-required>
<option ng-repeat="(state,city) in states[state1]" value="{{city}}">{{city}}</option>
</select>
</div>
<input type="email" ng-disable="myForm.user.email.$valid" ng-model="user.email" name="eamil" ng-required/>
<button ng-disable="myForm.user.email.$valid" ng-click="formsubmit();">submit</button>
</form>
</div>
SCRIPT:
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.formsubmit = function () {
}
$scope.states = {
"IN": [
"Delhi",
"Goa",
"Gujarat",
"Himachal Pradesh",
]
};
$scope.countries = {
IN: ["India"],
ZA: ["South Africa"],
AT: ["Austria"]
}
$scope.state1 = Object.keys($scope.countries)[0];
$scope.lastName = "Doe";
});
jsfiddle
<form role="form" name="signupForm" ng-submit="signup()" novalidate>
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="clearfix"> </div>
<div class="inputGroup">
<input type="text" id="su_username" name="username" class="form-control input-md"
ng-model="user.username" ng-minlength="8" required>
<span class="inputBar"></span>
<label translate="signup.form.username">Username</label>
<span class="text-danger" ng-show="signupForm.username.$dirty && signupForm.username.$invalid">
<span ng-show="signupForm.username.$error.required" translate="signup.messages.validate.username.required">Username is required.</span>
<span ng-show="signupForm.username.$error.minlength" translate="signup.messages.validate.username.minlength">Username must be at least 8 characters.</span>
</span>
</div>
</div>
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="clearfix"> </div>
<div class="inputGroup">
<input type="email" name="email" id="su_email" class="form-control input-md"
ng-model="user.email" required>
<span class="inputBar"></span>
<label translate="signup.form.email">Email Address</label>
<span class="text-danger" ng-show="signupForm.email.$dirty && signupForm.email.$invalid">
<span ng-show="signupForm.email.$error.required" translate="signup.messages.validate.email.required">Email is required.</span>
<span ng-show="signupForm.email.$error.email" translate="signup.messages.validate.email.invalid">Invalid email address.</span>
</span>
</div>
</div>
</div>
<button type="submit" class="btn btn-custom btn-lg btn-block"
ng-disabled="signupForm.$invalid ">
1st of all you need to give your form a name here its signupForm .
2nd from there you need to give your input fields names for example here they areusername and email.
Then you can use various angular validation directives to set validation constrains like require , length then you can check for validation error using signupForm.username.$invalid and check various error like signupForm.email.$error.email.
Finally if you want to check if the whole from is valid use signupForm.$invalid
and for number validation use
angular.module('test')
.directive('validNumber', function() {
return {
require: '?ngModel',
link: function(scope, element, attrs, ngModelCtrl) {
if(!ngModelCtrl) {
return;
}
ngModelCtrl.$parsers.push(function(val) {
if (angular.isUndefined(val)) {
val = '';
}
var clean = val.replace( /[^0-9\.]/g, '');
if (val !== clean) {
ngModelCtrl.$setViewValue(clean);
ngModelCtrl.$render();
}
return clean;
});
element.bind('keypress', function(event) {
if(event.keyCode === 32) {
event.preventDefault();
}
});
}
};
});
you can find github example from here
var app = angular.module('jsbin', []);
app.controller('DemoCtrl', function() {
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Angular JS</title>
</head>
<body ng-app="jsbin">
<div ng-controller="DemoCtrl as demo">
<form name="form" novalidate ng-submit="validate()">
<input type="email" name="email" ng-model="email" required />
<span class="help-inline" ng-show="submitted && form.email.$error.required">Required</span>
<span class="help-inline" ng-show="submitted && form.email.$error.email">Invalid email</span>
<button type="submit" class="btn btn-primary btn-large" ng-disabled="submitted && form.email.$error.required || submitted && form.email.$error.email" ng-click="submitted=true">Submit</button>
</form>
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"></script>
</body>
</html>
Check This Out.
In order to disable the submit button, you can do something like this:
<form name="myForm">
<input ...>
...
<button type="button" ng-disabled="myForm.$invalid" ng-click="formsubmit();">
Submit
</button>
</form>
Notice that I have put ng-disabled with a condition of myForm being invalid. So, instead of waiting for user to click the button, we are disabling the submit button upfront when form is invalid!
For Email validation, I would suggest you to go with <input type = "email"...> unless you have specific email validation requirements not handled by type = "email"
Here's the updated fiddle which disables the submit button until we put a valid email address.
Edit: Here's an example of how ng-pattern can be used to validate email for given rules (i.e. email must contain #, dot and after dot minimum 2 & maximum 4 alphabet characters)
<input type="text" ng-model="user.email" name="email" required
ng-pattern="/[a-zA-Z0-9_.]+\#[a-zA-Z0-9_]+\.[a-zA-Z]{2,4}$/"/>
Here's the updated fiddle
Also, regex101 for the email validation regex
I'm using the jQuery Validation Plugin, and I have a problem.
Both my jQuery and HTML is perfectly valid (according to JSLint and the WC3 Markup Validation Service), but when I hit the submit button, nothing happens.
My code even clearly states that, upon submitting, an alert should pop up, but even that doesn't work. The form is however validated correctly, and in the and, all fields are green (meaning they passed validation) but it doesn't actually submit (nothing happens).
Also, in my DevTools, the console does not report any errors.
Possibly/probably relevant; the email-textfield and the username-textfield are both being checked by a remote PHP script. This script works, strange enough, only after the second time. So when I first leave (blur) the email-textfield, nothing happens, it's isn't marked correct nor false.
Only when I (re-enter and) leave the textfield for the second time, it is validated, or when I hit the submit button. (This shows the submit button is actually connected, it just simply does not submit)
I really hope someone will solve my problem. I'm not trying to just let someone else do the work. I validated, checked, debugged, but nothing solved my problem.
My HTML (it's in a modal using Bootstrap):
<div class="modal fade" id="signupModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form class="form-horizontal" id="signupform" method="post" action="/" role="form">
<div class="modal-body" style="padding-bottom: 5px;">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<p class="lead">For creating an account,</p>
<p class="lead text-right">you'll have to give us some information.</p>
<div id="emailgroup" class="form-group">
<label for="signupemail"> Your Emailaddress</label>
<div class="col-lg-10">
<div class="input-group">
<span class="input-group-addon"> <span class="glyphicon glyphicon-envelope"></span> </span>
<input type="email" name="signupemail" spellcheck="false" autocomplete="on" required class="form-control" id="signupemail" placeholder="Email">
</div>
<label class="control-label error-label" for="signupemail"></label>
</div>
</div>
<div id="fnamegroup" class="form-group">
<label for="inputfname"> Your Full Name</label>
<div class="col-lg-10">
<div class="input-group">
<span class="input-group-addon"> <span class="glyphicon glyphicon-user"></span> </span>
<input type="text" name="fname" required class="form-control" id="inputfname" placeholder="Barack Obama">
</div>
<label class="control-label error-label" for="inputfname"></label>
</div>
</div>
<div id="unamegroup" class="form-group">
<label for="inputuname"> Your Username</label>
<div class="col-lg-10">
<div class="input-group">
<span class="input-group-addon"> # </span>
<input type="text" name="uname" required class="form-control" id="inputuname" placeholder="PresidentofAmerica">
</div>
<label class="control-label error-label" for="inputuname"></label>
</div>
</div>
<div id="thepasswordgroup" class="form-group">
<label for="thepassword"> Your Password</label>
<div class="col-lg-10">
<div class="input-group">
<span class="input-group-addon"> <span class="glyphicon glyphicon-lock"></span> </span>
<input type="password" name="thepassword" required class="form-control" id="thepassword" placeholder="123456789" autocomplete="off">
</div>
<label class="control-label error-label" for="thepassword"></label>
</div>
</div><br />
<div id="gendergroup">
<label>Your Gender</label>
<div class="radio">
<label class="checkbox-inline"><input type="radio" name="gendergroup" id="gendergroupmale" value="male" checked>I'm a Male</label>
</div>
<div class="radio">
<label class="checkbox-inline"><input type="radio" name="gendergroup" id="gendergroupfemale" value="female">I'm a Female</label>
</div>
</div>
<br />
<div class="form-group">
<label for="taccheckbox"> Terms and Conditions</label>
<div class="col-lg-10">
<div class="input-group"><span class="input-group-addon">
<input id="taccheckbox" name="taccheckbox" type="checkbox" required>
</span>
<input style="cursor:default !important;" type="text" id="something" value="I accept the Terms and Conditions" readonly class="form-control">
</div>
<label class="control-label error-label" for="taccheckbox"></label>
<!-- /input-group -->
</div>
<!-- /.col-lg-6 -->
</div>
</div>
<div class="modal-footer">
<p>
Have already got an account? <strong>Login here!</strong></p>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<input type="submit" id="signupsubmitbtn" class="btn btn-primary" value="Sign Up">
</div>
</form>
</div>
</div>
</div>
My Javascript/jQuery:
$('#signupform').validate({
rules: {
signupemail: {
required: true,
email: true,
remote: {
url: "/functions/verifysignup.php",
type: "post"
}
},
fname: {
required: true,
minlength: 8,
maxlength: 30
},
uname: {
required: true,
minlength: 6,
maxlength: 20,
remote: {
url: "/functions/verifysignup.php",
type: "post"
}
},
thepassword: {
required: true,
minlength: 5,
maxlength: 20
},
taccheckbox: "required"
},
messages: {
email: {
remote: "This emailaddress is already in use"
},
taccheckbox: {
required: "You have to accept the Terms and Conditions"
},
fname: {
minlength: "At least 8 characters required",
maxlength: "Max. 30 characters"
},
uname: {
minlength: "At least 6 characters required",
maxlength: "Max. 20 characters",
remote: "This username is already in use"
}
},
submitHandler: function (form) {
alert('called');
$('#signupsubmitbtn').prop("disabled", false);
//^[a-zA-Z0-9_-]{6,15}$ username
form.submit();
},
errorPlacement: function (error, element) {
if (element.attr('id') == "taccheckbox") {
error.appendTo(element.parent().parent().next());
} else {
error.appendTo(element.parent().next());
}
},
highlight: function (element) {
$(element).closest('.form-group').removeClass('has-success').addClass('has-error');
$('#signupsubmitbtn').prop("disabled", true);
},
unhighlight: function (element) {
$(element).closest('.form-group').removeClass('has-error').find('label.control-label label').text('');
if ($('.has-error').length === 0) {
$('#signupsubmitbtn').prop("disabled", false);
}
},
success: function (element) {
element.closest('.form-group').removeClass('has-error').addClass('has-success');
if ($('.has-error').length === 0) {
$('#signupsubmitbtn').prop("disabled", false);
}
//element.parent().next().text('');
}
});
My remote PHP script:
<?php
define ('INCLUDE_CHECK',true);
require('connect.php');
if(isset($_REQUEST['signupemail'])){
$email = mysqli_real_escape_string($link, $_REQUEST['signupemail']);
$thearray = mysqli_fetch_array(mysqli_query($link, "SELECT COUNT(*) FROM `users` WHERE email=\"".$email."\""));
if($thearray[0] > 0){
echo '"This emailaddress is already in use"';
} else {
echo "True";
}
} else if(isset($_REQUEST['uname'])){
$uname = mysqli_real_escape_string($link, $_REQUEST['uname']);
$thearray = mysqli_fetch_array(mysqli_query($link, "SELECT COUNT(*) FROM `users` WHERE uname=\"".$uname."\""));
$forbiddennames = array(1 => 'super-user','superuser', 'root', 'admin', 'administrator', 'system', 'website', 'site', 'owner', 'manager', 'founder','moderator');
if(in_array(strtolower($_REQUEST['uname']), $forbiddennames)) {
echo '"'.$_REQUEST['uname'].' is a forbidden username"';
} else if($thearray[0] > 0){
echo '"This username is already in use, please choose another"';
} else {
echo "True";
}
}
?>
Everything looks very close to correct to me. One issue is that you have your php script echoing True back, but it has to be true (lower case). That actually matters.
Otherwise, IMO your script looks fine.
The stuff you're saying about it not calling your submitHandler, or only triggering the remote bits doesn't really seem to be the case to me. I copied your code and simply added a bit of debugging (i.e. to console.log when remote gets triggered or when submitHandler gets called) and both got called at the appropriate times.
For instance, if you type a valid email and then click to the next field, it immediately validates the email address.
So whatever issues you're having, are not related to the code you've shown (except for that one error with true vs True).
Here's a working example of your code, for reference: http://jsfiddle.net/ryleyb/tWH9M/1/
In order to test it with remote working teh way you have it setup, you have to find this bit:
signupemail: {
required: true,
email: true,
remote: {
url: '/echo/json/',
data: {
json: function () {
return 'true';
}
},
complete: function (data) {
$('#log').append('remote signupemail triggered<br>');
},
type: 'post'
},
},
And change that return 'true'; to return 'True';