How to prevent a slow post form UX breaking required attribute? - html

I have a form that looks like:
<form method="POST">
<label>Your name:
<input name="name" required>
</label>
<input type="submit" onclick="this.form.submit(); this.disabled=true; this.value='Sending…';">
</form>
The form's backend takes upto 10 seconds to respond (blockchain!), hence the disabled input to prevent multiple retries. However it then breaks the required validation and users can send in empty payloads.
Any tips how to prevent this using Vanilla or maybe VueJS?
Many thanks,

Using Vuejs you can try :
#submit.prevent="action" in form tag instead of onClick....
add async await with try catch
if you want you can also disable your button in submit to be sure users can't send an empty payloads
here's a gist code : https://gist.github.com/jiyuuki/e6f8f7bb67b48014223d1561119ac2fa

Try to control the button with a data property like below.
export default {
name: 'Form',
data() {
return {
isLoading: false,
}
},
methods: {
async submitForm() {
this.isLoading = true;
await this.form.submit(); // Assuming this is where you make the time taking call
this.disabled=true;
this.value='Sending…';
}
}
}
<form method="POST">
<label>Your name:
<input name="name" required>
</label>
<input
:disabled="isLoading"
type="button"
#click="submitFrom">
</form>
Reset the isLoading to false after the response reaches.

Related

Is there a way to enable the autocomplete for angular reactive form?

I want to set on the autocomplete attribute for an angular form but it doesn't work as expected. It remembers only the values that I submitted only the first time and I would like to remember and suggest all the values no matter how many times I click on submit button.
Here is the stackblitz with the code that I tried.
<form
autocomplete="on"
(ngSubmit)="onSubmit()"
name="filtersForm"
[formGroup]="formGroup1"
>
<div>
<label>First Name</label>
<input
id="firstName"
name="firstName"
autocomplete="on"
formControlName="firstName"
/>
</div>
<div>
<label>Last Name</label>
<input
id="firstName"
name="lastName"
autocomplete="on"
formControlName="lastName"
/>
</div>
<button type="submit">Submit</button>
</form>
Here are the details about the autocomplete attribute that I used.
In Firefox, the autocomplete is working after several clicks on Submit button, the problem is in Chrome and Edge.
Is there a way to make the autocomplete to work for inputs inside the angular form?
I think, I have found a workaround, that only works with Template Driven Form.
TLDR;
What I have discovered while looking after this issue.
On first form submit autofill remember only first time submission values
form submit POST method can remember all values.
Yes, by looking at above, it clearly seems like 2nd way is suitable for us. But why would anybody do form POST for submitting form to BE. There should be better way to tackle this. Otherwise we would have to think of handling PostBack 😃😃 (FW like .Net does it by keeping hidden input's).
Don't worry we can do some workaround here to avoid form POST. I found an answer for handling POST call without page refresh.
Working JSBin with plain HTML and JS
AutoCompleteSaveForm = function(form){
var iframe = document.createElement('iframe');
iframe.name = 'uniqu_asdfaf';
iframe.style.cssText = 'position:absolute; height:1px; top:-100px; left:-100px';
document.body.appendChild(iframe);
var oldTarget = form.target;
var oldAction = form.action;
form.target = 'uniqu_asdfaf';
form.action = '/favicon.ico';
form.submit();
setTimeout(function(){
form.target = oldTarget;
form.action = oldAction;
document.body.removeChild(iframe);
});
}
Basically we change set few things on form attribute.
target="iframe_name" - Connects to iFrame to avoid page refresh.
method="POST" - POST call
url="/favicon" - API url to favicon (lightweight call)
In angular you can create an directive for the same.
import {
Directive, ElementRef, EventEmitter,
HostBinding, HostListener, Input, Output,
} from '#angular/core';
#Directive({
selector: '[postForm]',
})
export class PostFormDirective {
#HostBinding('method') method = 'POST';
#HostListener('submit', ['$event'])
submit($event) {
$event.preventDefault();
this.autoCompleteSaveForm(this.el.nativeElement);
}
constructor(private el: ElementRef) {}
autoCompleteSaveForm(form) {
let iframe = document.querySelector('iframe');
if (!iframe) {
iframe = document.createElement('iframe');
iframe.style.display = 'none';
}
iframe.name = 'uniqu_asdfaf';
document.body.appendChild(iframe);
var oldTarget = form.target;
var oldAction = form.action;
form.target = 'uniqu_asdfaf';
form.action = '/favicon.ico'; // dummy action
form.submit();
setTimeout(() => {
// set back the oldTarget and oldAction
form.target = oldTarget;
form.action = oldAction;
// after form submit
this.onSubmit.emit();
});
}
#Output() onSubmit = new EventEmitter();
ngOnDestroy() {
let iframe = document.querySelector('iframe');
if (iframe) {
document.body.removeChild(iframe);
}
}
}
Okay, so far everything went well. Then I started integrating this in formGroup(Model Driven Form), somehow it didn't worked. It does not store value next time these fields.
<form (ngSubmit)="onSubmit()" [formGroup]="formGroup1" autocomplete="on">
<div>
<label>First Name</label>
<input id="firstName" name="firstName" formControlName="firstName" />
</div>
<div>
<label>Last Name</label>
<input id="lastName" name="lastName" formControlName="lastName" />
</div>
<button>Submit</button>
</form>
Later I tried the same with Template Driven Form. It just worked like a charm! I did not went into the depth why it didn't work for Model Driven Form (perhaps that investigation could eat more time).
<form #form1="ngForm" ngForm postForm (onSubmit)="onSubmit(form1)">
<ng-container [ngModelGroup]="userForm">
<div>
<label>First Name</label>
<input name="firstName" [(ngModel)]="userForm.firstName" />
</div>
<div>
<label>Last Name</label>
<input name="lastName" [(ngModel)]="userForm.lastName" />
</div>
</ng-container>
<button>Submit</button>
</form>
Yeah, I just said in the begining it works only with Template Driven Form. So you would have to switch to Template. And one more important thing to note, you may think of creating dummy POST api call, that can be lightweight rather than hitting favicon.
Stackblitz
autocomplete attribute works only with submitted values. It has nothing to do with Angular.
https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
If you need some custom behavior then you are better off creating your own component to autocomplete user's input, this way you can use some default values, add more of them on blur etc.
You just need to remove autocomplete="on" in input tag. With chrome, we only add attribute autocomplete="on" in form element and it will be cached all value that user input into input text. Result will be like this:
<form
autocomplete="on"
(ngSubmit)="onSubmit()"
name="filtersForm"
[formGroup]="formGroup1"
>
<div>
<label>First Name</label>
<input
id="firstName"
name="firstName"
formControlName="firstName"
/>
</div>
<div>
<label>Last Name</label>
<input
id="firstName"
name="lastName"
formControlName="lastName"
/>
</div>
<button type="submit">Submit</button>
</form>
You have to create an array with your desired options which should be displayed as autocomplete. You can have a look here https://material.angular.io/components/autocomplete/examples, there are multiple examples which should help you. Even if you're not using Angular Material, the logic would be the same

Single Page With Multi Form Components - Single or multiple post request to submit forms using single submit button

I created a page with 3 components(divs) and each is having a html form with single submit button at the end of the page.
I would like to know what is the best design to submit the form using single submit button
Submit 3 forms in one post request.
3 post request for submiting 3 forms.
I am using AngularJS.
It all depends on the context of what you're trying to accomplish on the page. Ideally you'd have one single form with one submit button. The only reason I can see for having three forms with three submit buttons is if you do not require the other one or two forms to be submitted within the same session.
Although seeing as you're using angularjs you could collect everything into a json object and send it using $http
factory
app.factory('dataHandler',['$http', function($http){
var handle = this;
handle.send = function(params, actionUrl){
return $http({
url: actionUrl,
method: 'POST',
params: formParams,
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});
return handle;
}]);
controller
app.controller('Stage', ['dataHandler', '$scope', function(dataHandler, $scope){
var stage = this;
stage.processForm = function(){
dataHandler.sendOrder(stage.formInput, 'https://url.com/script')
.then(function (response) {
if (response.status == 200) {
console.log('Yay it worked');
}
});
}
}]);
html
<form id="Form" name="Form" novalidate="novalidate">
<input type="checkbox" ng-model="stage.formInput.thisOption" ng-true-value="1" ng-false-value="0">
<input type="text" name="address_street" ng-model="stage.formInput.details.address.street" required>
</form>
<form id="Form2" name="Form2" novalidate="novalidate">
<input type="checkbox" ng-model="stage.formInput.thatOption" ng-true-value="1" ng-false-value="0">
<input type="text" name="address_street" ng-model="stage.formInput.details.address.suburb" required>
</form>
<button ng-click="stage.processForm()" name="button">Submit</button>

ng-model vs ngModel - breaks form

New to angular, new to life:
I have a small email form.
This works:
<form method="post" name="form" role="form" ng-controller="contactForm" ng-submit="form.$valid && sendMessage(input)" novalidate class="form-horizontal">
<p ng-show="success"><b>We received your message</b></p>
<p ng-show="error">Something wrong happened!, please try again.</p>
<label for="name">Name:</label><br>
<input type="text" id="name" name="name" ng-model="input.name" required><br>
<label for="email">Email:</label><br>
<input type="email" id="email" name="email" ng-model="input.email" required><br>
<label for="messsage">Message:</label><br>
<textarea id="messsage" name="message" ng-model="input.message" ngMaxlength='2000' required></textarea><br>
<button type="submit" name="submit" ng-disabled="error" value="submit">Submit</button>
</form>
This does not work:
<form method="post" name="form" role="form" ng-controller="contactForm" ng-submit="form.$valid && sendMessage(input)" novalidate class="form-horizontal">
<p ng-show="success"><b>We received your message</b></p>
<p ng-show="error">Something wrong happened!, please try again.</p>
<label for="name">Name:</label><br>
<input type="text" id="name" name="name" ngModel="input.name" required><br>
<label for="email">Email:</label><br>
<input type="email" id="email" name="email" ngModel="input.email" required><br>
<label for="messsage">Message:</label><br>
<textarea id="messsage" name="message" ngModel="input.message" ngMaxlength='2000' required></textarea><br>
<button type="submit" name="submit" ng-disabled="error" value="submit">Submit</button>
</form>
for the 2 inputs and the textarea if I use 'ng-model' the email sends, but when the page loads, the form loads invalid.
If i use 'ngModel' the form loads clean, but the email wont submit.
controller here:
app.controller("contactForm", ['$scope', '$http', function($scope, $http) {
$scope.success = false;
$scope.error = false;
$scope.sendMessage = function( input ) {
$http({
method: 'POST',
url: 'processForm.php',
data: input,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
.success( function(data) {
if ( data.success ) {
$scope.success = true;
$scope.input.name="";
$scope.input.email="";
$scope.input.message="";
} else {
$scope.error = true;
}
} );
}
You can see it live here:
http://smartestdiner.com/Bethel/indexx.html#/contact
Warning:
There is some annoying red background
.ng-invalid{
background-color:red;
}
}]);
That's how we know it is loading invalidly.
The annoying red background is the form, since you have a very generic rule set by .ng-invalid, the class will be set on the form as well. You would need to make it more specific for the inputs and controls within the form.
Example:
input.ng-invalid,
textarea.ng-invalid {
background-color:red;
}
Or just reset rule for form.ng-invalid
To add on there is nothing called ngModel it is ng-model. using the former one doesn't do anything but adds a dummy attribute on the element, it has no effect. It is angular way of directive naming, since html is case insensitive the one way angular can identify the directive from attribute or element name (based on the restriction). It converts it to camelCasing to evaluate and process respective directive (or directives attribute bindings). When you do not have ng-model specified and if the form or control does not have novalidate attribute, then the browser's HTML5 validation kicks in that is what you see as inconsistency. Using HTML5 novalidate attribute makes sure no native validation happens on the form.
ng-model is when u write the view (html part).
ngModel is used when one write a custom directive. It is placed in the "require:" param so that u can access,
variables like ngModel.$modelValue
ngModel.$modelValue will have the latest content which has been typed by the user at realtime. So, it can be used for validations, etc.
View code:-
<!doctype html>
<html ng-app="plankton">
<head>
<script src="/bower_components/angular/angular.min.js"></script>
<script src="/scripts/emailing/emailing.directive.js"></script>
</head>
<body ng-controller="EmailingCtrl">
<div>
<label>Enter Email: </label>
<emailing id="person_email" ng-model="email_entered"></emailing>
</div>
</body>
</html>
Custom directive:-
(function() {
'use strict';
angular.module('plankton', [])
.directive('emailing', function emailing(){
return {
restrict: 'AE',
replace: 'true',
template: '<input type="text"></input>',
controllerAs: 'vm',
scope: {},
require: "ngModel",
link: function(scope, elem, attrs, ngModel){
console.log(ngModel);
scope.$watch(function(){ return ngModel.$modelValue;
}, function(modelValue){
console.log(modelValue);//awesome! gets live data entered into the input text box
});
},
};
})
.controller('EmailingCtrl', function($scope){
var vm = this;
});
})();
This has been plunked here:- here

How can I create a custom message when an HTML5 required input pattern does not pass?

I have the following:
<input required pattern=".{6,}" class="big medium-margin" name="Password" placeholder="Password" size="25" type="password" />
When I enter just one character I get a message saying:
"Please match the requested format"
Is there a way I can customize this message to say something like "Please enter at least 5 characters"
You can do a quick and dirty way with this trick:
<form>
<label for="username">Username:</label><br/>
<input id="username" type="text" pattern=".{6,}" autofocus required title="Please enter at least 5 characters">
<input id="submit" type="submit" value="create">
</form>
Use: setCustomValidity
First function sets custom error message:
$(function(){
$("input[name=Password]")[0].oninvalid = function () {
this.setCustomValidity("Please enter at least 5 characters.");
};
});
Second function turns off custom message. Without this function custom error message won't turn off as the default message would:
$(function(){
$("input[name=Password]")[0].oninput= function () {
this.setCustomValidity("");
};
});
P.S. you can use oninput for all input types that have a text input.
For input type="checkbox" you can use onclick to trigger when error should turnoff:
$(function(){
$("input[name=CheckBox]")[0].onclick= function () {
this.setCustomValidity("");
};
});
For input type="file" you should use change.
The rest of the code inside change function is to check whether the file input is not empty.
P.S. This empty file check is for one file only, feel free to use any file checking method you like as well as you can check whether the file type is to your likes.
Function for file input custom message handling:
$("input[name=File]").change(function () {
let file = $("input[name=File]")[0].files[0];
if(this.files.length){
this.setCustomValidity("");
}
else {
this.setCustomValidity("You forgot to add your file...");
}
//this is for people who would like to know how to check file type
function FileType(filename) {
return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename) : undefined;
}
if(FileType(file.name)!="pdf"||FileType(file.name)!="PDF"){
this.setCustomValidity("Your file type has to be PDF");
//this is for people who would like to check if file size meets requirements
else if(file.size/1048576>2){
// file.size divided by 1048576 makes file size units MB file.size to megabytes
this.setCustomValidity("File hast to be less than 2MB");
}
else{
this.setCustomValidity("");
}
});//file input custom message handling function
HTML5 form required attribute. Set custom validation message?
JSFiddle: http://jsfiddle.net/yT3w3/
Non-JQuery solution:
function attachHandler(el, evtname, fn) {
if (el.addEventListener) {
el.addEventListener(evtname, fn.bind(el), false);
} else if (el.attachEvent) {
el.attachEvent('on' + evtname, fn.bind(el));
}
}
attachHandler(window, "load", function(){
var ele = document.querySelector("input[name=Password]");
attachHandler(ele, "invalid", function () {
this.setCustomValidity("Please enter at least 5 characters.");
this.setCustomValidity("");
});
});
JSFiddle: http://jsfiddle.net/yT3w3/2/
I'd add another attribute oninvalid.
oninvalid="setCustomValidity('Please enter at least 5 characters')"
<input required pattern=".{6,}" class="big medium-margin" name="Password" placeholder="Password" size="25" type="password" oninvalid="setCustomValidity('Please enter at least 5 characters')"/>
I found that, chrome at least, adds to the message the title of the input automatically, so no extra js is required, see this:
the input looks like this:
<input type="text" title="Number with max 3 decimals" pattern="^\d+(\.\d{1,3})?$">
It is very simple without javascript or jQuery validation. We can achieve it by HTML5
Let suppose we have HTML field:
<input required pattern=".{6,}" class="big medium-margin" name="Password" placeholder="Password" size="25" type="password" />
Just change the HTML as
<input required pattern=".{6,}" class="big medium-margin" title="Please enter at least 5 characters." name="Password" placeholder="Password" size="25" type="password" />
If you observe, just add title = "Error message"
Now whenever form will be post, the given messages will be appeared and we did not need JavaScript or jQuery check.
This solution works for me.
I simply use oninvalid to set the custom validty error message and then use onchange to reset the message so the form can submit.
<input type="number" oninvalid="this.setCustomValidity('Please enter an INTEGER')" onchange="this.setCustomValidity('')" name="integer-only" value="0" min="0" step="1">
You'd need to use the setCustomValidity function. The problem with this is that it'd only guarantee a custom message for users who have JavaScript enabled.
<input required pattern=".{6,}" ... oninput="check(this)">
^^^^^^^^^^^^^^^^^^^^^
function check (input) {
if (input.value.search(new RegExp(input.getAttribute('pattern'))) >= 0) {
// Input is fine. Reset error message.
input.setCustomValidity('');
} else {
input.setCustomValidity('Your custom message here.');
}
}
https://www.geeksforgeeks.org/form-required-attribute-with-a-custom-validation-message-in-html5/
<input id="gfg" type="number" min="101" max="999" required>
<button onclick="myFunction()">Try it</button>
<p id="geeks"></p>
<script>
function myFunction() {
var inpObj = document.getElementById("gfg");
if (!inpObj.checkValidity()) {
document.getElementById("geeks")
.innerHTML = inpObj.validationMessage;
} else {
document.getElementById("geeks")
.innerHTML = "Input is ALL RIGHT";
}
}
</script>

Do Not Load Page After Form Submit

I have created a basic HTML contact form using cgimail and everything works, but I can't get it to keep from redirecting somewhere after the form is submitted. I'm trying to instead use a bootstrap alert at the top of the page.
How do I get the form to submit, then keep it from redirecting?
here's the code:
<form method="post" action="/cgi-bin/cgiemail/forms/email.txt">
<fieldset>
<h2 id="contact-header">Contact</h2>
<label>Name:</label>
<input type="text" name="yourname" placeholder="" autofocus>
<label>Email Address:</label>
<input type="email" name="email" value="" placeholder="">
<label>Phone:</label>
<input type="tel" name="phone" value="" placeholder="">
<label>Message:</label>
<textarea name="message" rows="2"></textarea>
<br>
<button type="submit" id="formSubmit" class="btn">Send</button>
<input type="hidden" name="success" value="">
</fieldset>
</form>
Thanks,
Ryan
The "action" attribute in your form is telling it to send the browser over to that email.txt, which would then have control over whether or not to redirect you to another page. By default it would at least redirect you to the email.txt page for the post, but odds are cgi is doing extra stuff when posting to that page.
Using jQuery AJAX, you can do the following (this code skips error checking):
$('form').submit(function() {
var data = { };
data.yourname = $(this).find('input[name="yourname"]').val();
data.message = $(this).find('textarea[name="message"]').val();
// do the same as above for each form field.
$.post("/cgi-bin/cgiemail/forms/email.txt", data, function() {
//add the alert to the form.
$('body').prepend('div class="alert">Success!</div>');
});
return false;
});
You have two straight-forward choices. You can use jQuery and its forms plugin to turn this into an ajax operation or you can roll your own equivalent. It would look something like this (using jQuery):
$('form').submit(function() {
... get all values.
... ajax post the values to the server.
return false;
});
If you're using jQuery, then you could try cancelling the submit event. First give your form an id
HTML:
<form id="myform" ...
JavaScript:
$('#myform').on('submit', function(e) {
e.preventDefault();
$.ajax({
url: '/cgi-bin/cgiemail/forms/email.txt',
type: 'post',
data: $(this).serialize()
});
return false;
});