Now the button is activated if one of the fields is valid.
As I understand it, it is necessary to select all errors, and if there are more than 1 errors - disabled
Code validation
var forms = document.querySelectorAll('.validate');
for (var i = 0; i < forms.length; i++) {
forms[i].setAttribute('novalidate', true);
forms[i].lastElementChild.setAttribute('disabled', true);
}
// function has error
var hasError = function(field) {
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
var validity = field.validity;
if (validity.valid) {
return;
}
if (validity.valueMissing) {
return 'This is a required field';
}
if (validity.typeMismatch) {
// URL
if (field.type === 'url') {
return 'There should be a link';
}
}
if (validity.tooShort) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
if (validity.tooLong) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
return 'Error';
};
// function show error
var showError = function(field, error) {
field.classList.add('error');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id);
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
field.parentNode.insertBefore(message, field.nextSibling);
}
field.setAttribute('aria-describedby', 'error-for-' + id);
message.innerHTML = error;
message.style.display = 'block';
message.style.visibility = 'visible';
};
// function remove error
var removeError = function(field) {
field.classList.remove('error');
field.removeAttribute('aria-describedby');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id + '');
if (!message) return;
message.innerHTML = '';
message.style.display = 'none';
message.style.visibility = 'hidden';
};
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
// function check fields
function validationForm(event) {
if (!event.target.classList.contains('validate')) return;
var fields = event.target.elements;
var error, hasErrors;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
showError(fields[i], error);
if (!hasErrors) {
hasErrors = fields[i];
}
}
}
if (hasErrors) {
event.preventDefault();
hasErrors.focus();
}
}
document.addEventListener('input', checkInput);
document.addEventListener('submit', validationForm);
input {
display: inline-block;
font-size: 1em;
margin-bottom: 5px;
padding: 0.25em 0.5em;
width: 100%;
}
.button[disabled],
.button[disabled]:active,
.button[disabled]:focus,
.button[disabled]:hover {
border-color: #ccc;
background-color: #ccc;
}
.button {
background-color: #0088cc;
border: 1px solid #0088cc;
color: #ffffff;
display: inline-block;
font-size: 0.9375em;
font-weight: normal;
line-height: 1.2;
margin-right: 0.3125em;
margin-bottom: 0.3125em;
padding: 0.5em 0.6875em;
width: auto;
}
.button:active,
.button:focus,
.button:hover {
background-color: #005580;
border-color: #005580;
color: #ffffff;
text-decoration: none;
}
/**
* Errors
*/
.error {
border-color: red;
}
.error-message {
color: red;
font-size: 1rem;
margin-bottom: 15px;
}
<form class="validate">
<div>
<input type="text" minlength="3" maxlength="40" name="name" placeholder="Enter name" required>
</div>
<div>
<input type="url" name="url" pattern="^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?#)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*(?:\.(?:[a-zA-Z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$"
placeholder="Enter url" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
<br>
<form class="validate">
<div>
<input type="user" minlength="4" maxlength="40" id="user" placeholder="Enter user" required>
</div>
<div>
<input type="position" minlength="5" maxlength="50" id="position" placeholder="Enter position" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
I guess this is the problem part of the code
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
I expect the button to activate only if both fields have passed validation.
But now the button is activated if one of the fields is valid
You would need to update your check function. Current function is just Validating input that you are changing as you have added change input listener. you would need to add loop that Validate other fields for error.
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
//To Check All fields If There is still an error
var fields = event.target.parentElement.parentElement.elements;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
return;
}
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
The solution:
var forms = document.querySelectorAll('.validate');
for (var i = 0; i < forms.length; i++) {
forms[i].setAttribute('novalidate', true);
forms[i].lastElementChild.setAttribute('disabled', true);
}
// function has error
var hasError = function(field) {
if (field.disabled || field.type === 'file' || field.type === 'reset' || field.type === 'submit' || field.type === 'button') return;
var validity = field.validity;
if (validity.valid) {
return;
}
if (validity.valueMissing) {
return 'This is a required field';
}
if (validity.typeMismatch) {
// URL
if (field.type === 'url') {
return 'There should be a link';
}
}
if (validity.tooShort) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
if (validity.tooLong) {
return 'Must be from ' + field.getAttribute('minLength') + ' to ' + field.getAttribute('maxLength') + ' characters';
}
return 'Error';
};
// function show error
var showError = function(field, error) {
field.classList.add('error');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id);
if (!message) {
message = document.createElement('div');
message.className = 'error-message';
message.id = 'error-for-' + id;
field.parentNode.insertBefore(message, field.nextSibling);
}
field.setAttribute('aria-describedby', 'error-for-' + id);
message.innerHTML = error;
message.style.display = 'block';
message.style.visibility = 'visible';
};
// function remove error
var removeError = function(field) {
field.classList.remove('error');
field.removeAttribute('aria-describedby');
var id = field.id || field.name;
if (!id) return;
var message = field.form.querySelector('.error-message#error-for-' + id + '');
if (!message) return;
message.innerHTML = '';
message.style.display = 'none';
message.style.visibility = 'hidden';
};
// function check field
function checkInput(event) {
if (!event.target.form.classList.contains('validate')) return;
var error = hasError(event.target);
if (error) {
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
showError(event.target, error);
return;
}
//To Check All fields If There is still an error
var fields = event.target.parentElement.parentElement.elements;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.setAttribute('disabled', true);
return;
}
}
removeError(event.target);
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
}
// function check fields
function validationForm(event) {
if (!event.target.classList.contains('validate')) return;
var fields = event.target.elements;
var error, hasErrors;
for (var i = 0; i < fields.length; i++) {
error = hasError(fields[i]);
if (error) {
showError(fields[i], error);
if (!hasErrors) {
hasErrors = fields[i];
}
}
}
if (hasErrors) {
event.preventDefault();
hasErrors.focus();
}
}
document.addEventListener('input', checkInput);
document.addEventListener('submit', validationForm);
input {
display: inline-block;
font-size: 1em;
margin-bottom: 5px;
padding: 0.25em 0.5em;
width: 100%;
}
.button[disabled],
.button[disabled]:active,
.button[disabled]:focus,
.button[disabled]:hover {
border-color: #ccc;
background-color: #ccc;
}
.button {
background-color: #0088cc;
border: 1px solid #0088cc;
color: #ffffff;
display: inline-block;
font-size: 0.9375em;
font-weight: normal;
line-height: 1.2;
margin-right: 0.3125em;
margin-bottom: 0.3125em;
padding: 0.5em 0.6875em;
width: auto;
}
.button:active,
.button:focus,
.button:hover {
background-color: #005580;
border-color: #005580;
color: #ffffff;
text-decoration: none;
}
/**
* Errors
*/
.error {
border-color: red;
}
.error-message {
color: red;
font-size: 1rem;
margin-bottom: 15px;
}
<form class="validate">
<div>
<input type="text" minlength="3" maxlength="40" name="name" placeholder="Enter name" required>
</div>
<div>
<input type="url" name="url" pattern="^(?:(?:https?|HTTPS?|ftp|FTP):\/\/)(?:\S+(?::\S*)?#)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)(?:\.(?:[a-zA-Z\u00a1-\uffff0-9]-*)*[a-zA-Z\u00a1-\uffff0-9]+)*(?:\.(?:[a-zA-Z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$"
placeholder="Enter url" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
<br>
<form class="validate">
<div>
<input type="user" minlength="4" maxlength="40" id="user" placeholder="Enter user" required>
</div>
<div>
<input type="position" minlength="5" maxlength="50" id="position" placeholder="Enter position" required>
</div>
<input type="submit" class="button" value="Submit">
</form>
The reason for it not to work as expected is that you remove the disabled state if there's no error by calling
event.target.parentElement.parentElement.lastElementChild.removeAttribute('disabled');
but don't add the Attribute disabled if there is an error.
So if you leave the name input blank but add e.g. http://example.com the button is enabled as the URL triggered the removeAttribute() function.
As mplungjan suggested it might be better to just use required attribute.
Related
I have an HTML form within a Web App, created with GAS.
This HTML form was created from this file upload script here:
drive-multi-upload
This is the HTML form:
The point is that I needed the files to be uploaded in folders that follow the following pattern:
The first number refers to the model selected on the form, the second number refers to the slot used.
Therefore, it was necessary to create a function to identify the input chosen in the Model and, according to this input, check which is the first empty folder, then take the ID of that folder and pass it to the client side to upload the file inside it.
With the help of some members of the community, some adaptations were made and the final function was this:
/** Modified version of script written by Tanaike */
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
var videoFolder = foldersIter.next();
var nextFolderName = folderName + "-01";
while (!folder) {
video_folder = videoFolder.getFoldersByName(nextFolderName);
if (video_folder.hasNext()) {
folder = video_folder.next();
var files = folder.getFiles();
if (files.hasNext()) {
var [a, b] = nextFolderName.split("-");
nextFolderName = `${a}-${String(Number(b) + 1).padStart(2, "0")}`;
folder = null;
}
} else {
folder = videoFolder.createFolder(nextFolderName);
}
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " + parentFolderId + " not found");
}
return folder.getId();
} catch (error) {
return error;
}
}
It works perfectly, the point is that this form also has a function that generates a .csv file when the form is submitted, the function is this one:
.gs file:
const saveDataAsCSV = (data, folderId) => DriveApp.getFolderById(folderId).createFile("Sample.csv", data);
HTML file:
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
var data = form_values.join(",");
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
My goal is to make the .csv file be generated with the same name as the folder where the file was uploaded, that is, if the file is uploaded in folder 01-01, the file name has to be 01-01.csv, if the file is uploaded in the 02-02 folder, the file name has to be 02-02.csv, and so on.
How can I achieve this?
The complete script code can be viewed here:
.gs file:
function doGet(e) {
return HtmlService.createTemplateFromFile('forms0101.html').evaluate();
}
function getOAuthToken() {
return ScriptApp.getOAuthToken();
}
function getParent(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var id = ss.getId();
var parent = DriveApp.getFileById(id).getParents().next().getId();
return parent
}
function getLimitFolder(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastapai = DriveApp.getFileById(ss.getId()).getParents();
var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
return limitfolder;
}
function getFilesLimit(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var pastapai = DriveApp.getFileById(ss.getId()).getParents();
var files = [];
Logger.log(pastapai);
//var limitfolder = pastapai.next().getFoldersByName("_").next().getId();
var limitfolder = pastapai.next().getFoldersByName("_").next().getFiles();
while(limitfolder.hasNext()){
var file = limitfolder.next();
files.push([file.getId()]);
}
console.log(files.length);
return files.length;
}
//function testenumeroarquivos(){
//console.log(checkForFiles()); // When you use this line, you can see the filename of the files.
//}
/**
* creates a folder under a parent folder, and returns it's id. If the folder already exists
* then it is not created and it simply returns the id of the existing one
*/
function createOrGetFolder(folderName, parentFolderId) {
try {
var parentFolder = DriveApp.getFolderById(parentFolderId), folder;
if (parentFolder) {
var foldersIter = parentFolder.getFoldersByName("Video");
if (foldersIter.hasNext()) {
var videoFolder = foldersIter.next();
var nextFolderName = folderName + "-01";
while (!folder) {
video_folder = videoFolder.getFoldersByName(nextFolderName);
if (video_folder.hasNext()) {
folder = video_folder.next();
var files = folder.getFiles();
if (files.hasNext()) {
var [a, b] = nextFolderName.split("-");
nextFolderName = `${a}-${String(Number(b) + 1).padStart(2, "0")}`;
folder = null;
}
} else {
folder = videoFolder.createFolder(nextFolderName);
}
}
} else {
folder = parentFolder.createFolder("Video");
folder = folder.createFolder(folderName);
}
} else {
throw new Error("Parent Folder with id: " + parentFolderId + " not found");
}
console.log("Test" + nextFolderName)
return folder.getId();
} catch (error) {
return error;
}
}
const saveDataAsCSV = (data, folderId) => {
const folder = DriveApp.getFolderById(folderId);
folder.createFile(`${nextFolderName.getName()}.csv`, data);
}
const increaseRequest = (data, folderId) => DriveApp.getFolderById(folderId).createFile("IncreaseRequest.csv", data);
// NOTE: always make sure we use DriveApp, even if it's in a comment, for google to import those
// libraries and allow the rest of the app to work. see https://github.com/tanaikech/Resumable_Upload_For_WebApps
HTML file:
<!DOCTYPE html>
<html>
<head>
<base target="_blank">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drive Multi Large File Upload</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<style>
#import url('https://fonts.googleapis.com/css2?family=Rubik:wght#400;600;700&display=swap');
.disclaimer{
width: 480px;
color: #646464;
margin: 20px auto;
padding:0 16px;
text-align:center;
font:400 12px Rubik,sans-serif;
}
h5.center-align.teal-text {
font:700 26px Rubik,sans-serif;
color: #00F498!important;
}
.row {
font:600 14px Rubik,sans-serif;
}
.btn {
background-color: black;
}
.btn:hover {
background-color: #00F498;
}
body {
margin-top: -40px;
}
#progress {
color: #00000;
}
.disclaimer a{
color: #00BCAA;
}
#credit{
display:none
}
.btnOpenModal {
font-size: x-large;
padding: 10px;
border: none;
border-radius: 5px;
background-color: blueviolet;
color: white;
cursor: pointer;
}
hr {
border: 0px;
border-top: 1px solid lightgray;
}
.modal-container {
width: 100vw;
position: fixed;
top: 30px;
display: none;
z-index: 999;
justify-content: center;
}
.modal {
display: flex;
flex-direction: column;
padding: 30px;
background-color: white;
border-radius: 10px;
width: 50%;
}
.active {
display: flex;
}
.active .modal {
animation: modal .4s;
}
#keyframes modal {
from {
opacity: 0;
transform: translate3d(0, -60px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.btns {
display: flex;
justify-content: space-between;
margin-top: 10px;
}
.btns button {
font-size: medium;
padding: 10px;
border: none;
border-radius: 5px;
color: white;
cursor: pointer;
}
.btnOK {
background-color: blue!important;
}
.btnClose {
background-color: brown!important;
}
</style>
</head>
<body>
<form class="main" id="form" novalidate="novalidate" style="max-width: 480px;margin: 40px auto;">
<div id="forminner">
<div class="modal-container">
<div class="modal">
<h2>Info</h2>
<hr />
<span>
Lorem Ipsum is simply dummy text of the printing and typesetting
industry. Lorem Ipsum has been the industry's standard dummy text
ever since the 1500s, when an unknown printer took a galley of
type and scrambled it to make a type specimen book.
</span>
<hr />
<div class="btns">
<button class="btnOK" onclick="increaseRequest(); closeModal();">OK</button>
<button class="btnClose" onclick="closeModal()">Close</button>
</div>
</div>
</div>
<h5 class="center-align teal-text" style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">YOUR NAME</h5>
<div class="row">
<div class="input-field col s12">
<input id="name01" type="text" name="Name" class="validate" required="required" aria-required="true">
<label for="name" class="">Name</label>
</div>
</div>
<h5 class="center-align teal-text" style="margin-bottom: -10px; font-size: 20px; font-family: Rubik; ">SOME DESCRIPTION</h5>
<div class="row">
<div class="input-field col s12">
<input id="description" type="text" name="Description" class="validate" required="required" aria-required="true">
<label for="name">Description</label>
</div>
</div>
<div class="row">
<div class="col-8 col-md-4">
<h6>Model</h6>
<select class="custom-select" id="Model">
<option selected="">Choose...</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
</select>
<h6>Color</h6>
<select class="custom-select" id="Color">
<option selected="">Choose...</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
</select>
</div>
</div>
<div class="row">
<div class="col s12">
<h5 class="center-align teal-text">Upload the Video File</h5>
</div>
</div>
<div class="row">
<div class="file-field input-field col s12">
<div id="input-btn" class="btn">
<span>File</span>
<input id="files" type="file" single="">
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="Select the file">
</div>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<button id="submit-btn" class="waves-effect waves-light btn submit-btn" type="submit" onclick="submitForm(); return false;">Submit</button>
</div>
</div>
<div class="row">
<div class="input-field col s12 hide" id="update">
<hr>
<p>
Por favor, aguarde enquanto seu arquivo está sendo carregado.<br><span style="color: #00000;"><b>Não feche ou atualize a janela durante o upload.</b></span>
</p>
</div>
</div>
<div class="row">
<div class="input-field col s12" id="progress">
</div>
</div>
</div>
</div>
<div id="success" style="display:none">
<h5 class="center-align teal-text">Tudo certo!</h5>
<p>Se você já preencheu todos os campos é só fechar essa janela e clicar em enviar!</p>
<button id="fechar" class="waves-effect waves-light btn submit-btn" style ="transform: translateX(160%);" type="button" onclick="google.script.host.close()">Fechar</button>
</div>
</form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
<script>
var upload_folder = "01";
const chunkSize = 5242880;
const uploadParentFolderId = <?=getParent()?>; // creates a folder inside of this folder
const limitfolder = <?=getLimitFolder()?>;
const fileslimit = <?=getFilesLimit()?>;
const modal = document.querySelector('.modal-container');
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
var data = form_values.join(",");
function submitForm() {
var name = $('#name01').val();
var description = $('#description').val();
var model = $('#Model').val();
upload_folder = model;
var color = $('#Color').val();
var form_values = [name, description, model, color];
form_values = form_values.map(r => r.replaceAll(",", "#")); // Essa linha substitui todas as "," por "#" antes de gerar o .csv
var data = form_values.join(",");
// Added the below script.
if ($('#submit-btn.disabled')[0]) return; // short circuit
var name = upload_folder
var files = [...$('#files')[0].files]; // convert from FileList to array
if (files.length === 0) {
showError("Por favor, selecione um arquivo");
return;
}
google.script.run.saveDataAsCSV(data, uploadParentFolderId);
google.script.run.saveDataAsCSV(data, limitfolder);
disableForm(); // prevent re submission
// the map and reduce are here to ensure that only one file is uploaded at a time. This allows
// the promises to be run sequentially
files.map(file => uploadFilePromiseFactory(file))
.reduce((promiseChain, currentTask) => {
return promiseChain.then(currentTask);
}, Promise.resolve([])).then( () => {
console.log("Completed all files upload");
showSuccess();
});
}
function checkforfiles() {
if (fileslimit <= 2) {
submitForm();
}
if (fileslimit == 3) {
openModal();
}
if (fileslimit >= 4) {
showError("erro");
return;
}
}
function openModal() {
modal.classList.add('active');
}
function closeModal() {
modal.classList.remove('active');
}
function increaseRequest() {
google.script.run.increaseRequest(data, limitfolder);
}
function disableForm() {
$('#submit-btn').addClass('disabled');
$('#input-btn').addClass('disabled');
$('#update').removeClass('hide');
$('#update').removeClass('hide');
}
function closer(){
google.script.host.close();
}
function uploadFilePromiseFactory(file) {
return () => {
console.log("Processing: ", file.name);
return new Promise((resolve, reject) => {
showProgressMessage("Seu arquivo está sendo carregado");
var fr = new FileReader();
fr.fileName = file.name;
fr.fileSize = file.size;
fr.fileType = file.type;
// not sure of a better way of passing the promise functions down
fr.resolve = () => resolve();
fr.reject = (error) => reject(error);
fr.onload = onFileReaderLoad;
fr.readAsArrayBuffer(file);
});
};
}
/**
* Gets called once the browser has loaded a file. The main logic that creates a folder
* and initiates the file upload resides here
*/
function onFileReaderLoad(onLoadEvent) {
var fr = this;
var newFolderName = upload_folder
createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
console.log("Found or created guest folder with id: ", newFolderId);
uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
fr.resolve();
}, (error) => {
fr.reject(error);
});
},
(error) => {
if (error) {
showError(error.toString());
}
console.log("onFileReaderLoad Error2: ", error);
});
}
/**
* call to the DriveApp api. Wrapped in a promise in case I want to address timing issues between a
* createFolder and findFolderById
*/
function createOrGetFolder(folderName, parentFolderId) {
return new Promise((resolve, reject) => {
google.script.run.withSuccessHandler(response => {
console.log("createOrGetFolder response: ", response);
if (response && response.length) {
resolve(response);
}
reject(response);
}).createOrGetFolder(folderName, parentFolderId);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function uploadFileToDriveFolder(parentFolderId) {
var fr = this;
return new Promise((resolve, reject) => {
var fileName = fr.fileName;
var fileSize = fr.fileSize;
var fileType = fr.fileType;
console.log({fileName: fileName, fileSize: fileSize, fileType: fileType});
var buf = fr.result;
var chunkpot = getChunkpot(chunkSize, fileSize);
var uint8Array = new Uint8Array(buf);
var chunks = chunkpot.chunks.map(function(e) {
return {
data: uint8Array.slice(e.startByte, e.endByte + 1),
length: e.numByte,
range: "bytes " + e.startByte + "-" + e.endByte + "/" + chunkpot.total,
};
});
google.script.run.withSuccessHandler(oAuthToken => {
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");
xhr.setRequestHeader('Authorization', "Bearer " + oAuthToken);
xhr.setRequestHeader('Content-Type', "application/json");
xhr.send(JSON.stringify({
mimeType: fileType,
name: fileName,
parents: [parentFolderId]
}));
xhr.onload = () => {
doUpload(fileName, {
location: xhr.getResponseHeader("location"),
chunks: chunks,
}).then(success => {
resolve(success);
console.log("Successfully uploaded: ", fileName);
},
error => {
reject(error);
});
};
xhr.onerror = () => {
console.log("ERROR: ", xhr.response);
reject(xhr.response);
};
}).getOAuthToken();
});
}
function showSuccess() {
$('#forminner').hide();
$('#success').show();
$('#fechar').show();
}
function showError(e) {
$('#progress').addClass('red-text').html(e);
}
function showMessage(e) {
$('#update').html(e);
}
function showProgressMessage(e) {
$('#progress').removeClass('red-text').html(e);
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function doUpload(fileName, e) {
return new Promise((resolve, reject) => {
showProgressMessage("Carregando: <span style='color: #00F498 ;'>" + "0%</span>");
var chunks = e.chunks;
var location = e.location;
var cnt = 0;
var end = chunks.length;
var temp = function callback(cnt) {
var e = chunks[cnt];
var xhr = new XMLHttpRequest();
xhr.open("PUT", location, true);
console.log("content range: ", e.range);
xhr.setRequestHeader('Content-Range', e.range);
xhr.send(e.data);
xhr.onloadend = function() {
var status = xhr.status;
cnt += 1;
console.log("Uploading: " + status + " (" + cnt + " / " + end + ")");
showProgressMessage("Carregando: <span style='color: #00F498 ;'>"
+ Math.floor(100 * cnt / end) + "%</span>" );
if (status == 308) {
callback(cnt);
} else if (status == 200) {
$("#progress").text("Done.");
resolve();
} else {
$("#progress").text("Error: " + xhr.response);
reject();
}
};
}(cnt);
});
}
/**
* Helper functions modified from:
* https://github.com/tanaikech/Resumable_Upload_For_WebApps
*/
function getChunkpot(chunkSize, fileSize) {
var chunkPot = {};
chunkPot.total = fileSize;
chunkPot.chunks = [];
if (fileSize > chunkSize) {
var numE = chunkSize;
var endS = function(f, n) {
var c = f % n;
if (c == 0) {
return 0;
} else {
return c;
}
}(fileSize, numE);
var repeat = Math.floor(fileSize / numE);
for (var i = 0; i <= repeat; i++) {
var startAddress = i * numE;
var c = {};
c.startByte = startAddress;
if (i < repeat) {
c.endByte = startAddress + numE - 1;
c.numByte = numE;
chunkPot.chunks.push(c);
} else if (i == repeat && endS > 0) {
c.endByte = startAddress + endS - 1;
c.numByte = endS;
chunkPot.chunks.push(c);
}
}
} else {
var chunk = {
startByte: 0,
endByte: fileSize - 1,
numByte: fileSize,
};
chunkPot.chunks.push(chunk);
}
return chunkPot;
}
</script>
</body>
</html>
It can also be viewed by accessing this worksheet
Although I'm not sure whether I could correctly understand your actual expected result, please test the following modification.
Google Apps Script side:
Please modify saveDataAsCSV of Google Apps Script as follows.
const saveDataAsCSV = (data, folderId1, folderId2 = null) => {
if (folderId2) {
DriveApp.getFolderById(folderId1).createFile(`${DriveApp.getFolderById(folderId2).getName()}.csv`, data);
} else {
DriveApp.getFolderById(folderId1).createFile("sample.csv", data);
}
}
Javascript side:
Please remove google.script.run.saveDataAsCSV(data, uploadParentFolderId); in the function submitForm.
And, please modify onFileReaderLoad of Javascript as follows.
function onFileReaderLoad(onLoadEvent) {
var fr = this;
var newFolderName = upload_folder
createOrGetFolder(newFolderName, uploadParentFolderId).then(newFolderId => {
google.script.run.saveDataAsCSV(data, uploadParentFolderId, newFolderId);
console.log("Found or created guest folder with id: ", newFolderId);
uploadFileToDriveFolder.call(fr, newFolderId).then(() => {
fr.resolve();
}, (error) => {
fr.reject(error);
});
},
(error) => {
if (error) {
showError(error.toString());
}
console.log("onFileReaderLoad Error2: ", error);
});
}
By this modification, I think that a CSV file like 01-01.csv is created to uploadParentFolderId folder.
I am trying to scan a document with the click of a button in javascript. This is my code:
HTML:
<div class="mb-3" style="width: 50%; margin: 0 auto; border: 2px solid white;">
<label for="scannerName">Select preferred scanner :</label><br>
<select name="scannerName" id="scannerName" style="text-align: center; width: 50%;"></select><br><br>
<label for="pixelMode">Select pixel mode :</label><br>
<select name="pixelMode" id="pixelMode" style="text-align: center; width: 50%;"></select><br><br>
<label for="resolution">Select resolution :</label><br>
<select name="resolution" id="resolution" style="text-align: center; width: 50%;"></select><br><br>
<label for="imageFormat">Select image format :</label><br>
<select name="imageFormat" id="imageFormat" style="text-align: center; width: 50%;"></select><br><br>
</div>
<div class="d-grid gap-*" style="position: absolute; top: 100%; left: 46%;">
<button type="button" class="btn btn-primary btn-block" onclick="doScanning()">Upload</button>
</div>
JS:
var scannerDevices = null;
var _this = this;
//JSPrintManager WebSocket settings
JSPM.JSPrintManager.auto_reconnect = true;
JSPM.JSPrintManager.start();
JSPM.JSPrintManager.WS.onStatusChanged = function () {
if (jspmWSStatus()) {
//get scanners
JSPM.JSPrintManager.getScanners().then(function (scannersList) {
scannerDevices = scannersList;
var options = '';
if(scannerDevices.length>0){
for (var i = 0; i < scannerDevices.length; i++) {
options += '<option>' + scannerDevices[i] + '</option>';
}
}
else{
var options_new='';
options_new += '<option>' + "Not applicable" + '</option>';
$('#pixelMode').html(options_new);
$('#resolution').html(options_new);
$('#imageFormat').html(options_new);
options += '<option>' + "No scanners found installed" + '</option>';
}
$('#scannerName').html(options);
});
}
};
//Check JSPM WebSocket status
function jspmWSStatus() {
if (JSPM.JSPrintManager.websocket_status == JSPM.WSStatus.Open)
return true;
else if (JSPM.JSPrintManager.websocket_status == JSPM.WSStatus.Closed) {
console.warn('JSPrintManager (JSPM) is not installed or not running! Download JSPM Client App from https://neodynamic.com/downloads/jspm');
return false;
}
else if (JSPM.JSPrintManager.websocket_status == JSPM.WSStatus.Blocked) {
alert('JSPM has blocked this website!');
return false;
}
}
//Do scanning...
function doScanning() {
if (jspmWSStatus()) {
//create ClientScanJob
var csj = new JSPM.ClientScanJob();
//scanning settings
csj.scannerName = $('#scannerName').val();
csj.pixelMode = JSPM.PixelMode[$('#pixelMode').val()];
csj.resolution = parseInt($('#resolution').val());
csj.imageFormat = JSPM.ScannerImageFormatOutput[$('#imageFormat').val()];
let _this = this;
//get output image
csj.onUpdate = (data, last) => {
if (!(data instanceof Blob)) {
console.info(data);
return;
}
var imgBlob = new Blob([data]);
if (imgBlob.size == 0) return;
var data_type = 'image/jpg';
if (csj.imageFormat == JSPM.ScannerImageFormatOutput.PNG) data_type = 'image/png';
//create html image obj from scan output
var img = URL.createObjectURL(imgBlob, { type: data_type });
//scale original image to be screen size friendly
/*var imgScale = { width: Math.round(96.0 / csj.resolution * 100.0) + "%", height: 'auto' };
$('#scanOutput').css(imgScale);
$('#scanOutput').attr("src", img);*/
}
csj.onError = function (data, is_critical) {
console.error(data);
};
//Send scan job to scanner!
csj.sendToClient().then(data => console.info(data));
}
}
I've followed each and every step mentioned in this site:
https://www.neodynamic.com/articles/How-to-scan-documents-images-from-Javascript/
But I keep getting the message "No scanners are installed" in my scannerName select tag. In fact, when I go to the JS console, I get this message:
JSPrintManager License: Objectresult: "Invalid License"[[Prototype]]:
Object...
My scanner is a Canon canoscan lide 25. It's TWAIN compatible so it should be detected. How do I fix this issue? Do I have to buy the license?
When click on the submit button, I want to make sure it can detect if the user correctly entered the forms through the functions below, if user did not enter correctly it can stop the form from being submit.
What should I include in my AllValidate function?*
<button name="regbtn" id="register_btn" class="reg_btn" type="submit" onclick="AllValidate()">Register</button>
function addressValidate()
{
var address = document.getElementById("reg_add").value;
var space = /^\s*$/;
var flag = true;
if(address.match(space))
{
document.getElementById("add_error").innerHTML = "Address is required.";
document.getElementById("reg_add").style.border = "2px solid red";
flag = true;
}
else
{
document.getElementById("add_error").innerHTML = "";
document.getElementById("reg_add").style.border = "2px solid #f0f0f0";
flag = false;
}
return flag;
}
function phoneValidate()
{
var phoneNo = document.getElementById("reg_phone").value;
var pattern = /[0-9]{3}-[0-9]{4}-[0-9]{4}/;
var space = /^\s*$/;
var flag = true;
if(phoneNo.match(space))
{
document.getElementById("phone_error").innerHTML = "Phone number is required.";
document.getElementById("reg_phone").style.border = "2px solid red";
flag = true;
}
else if(phoneNo.match(pattern))
{
document.getElementById("phone_error").innerHTML = "";
document.getElementById("reg_phone").style.border = "2px solid #f0f0f0";
flag = false;
}
else
{
document.getElementById("phone_error").innerHTML = "Please enter a valid phone number.";
document.getElementById("reg_phone").style.border = "2px solid red";
flag = true;
}
return flag;
}
AllValidate()
{
}
First you have to edit the submit button as follows
<button name="regbtn" id="register_btn" class="reg_btn" type="submit" onclick="return AllValidate()">Register</button>
Then your AllValidate() function follows
function AllValidate(){
return (addressValidate() && phoneValidate());
}
use that way, with HTML5 auto validation
use attribute required
use attribute pattern ( you can also use input type="tel")
sample code
// personnalize your error message
const myForm = document.forms['my-form']
myForm.reg_phone.oninvalid=_=>
{
let msg = (myForm.reg_phone.value.trim() === '')
? 'Phone number is required.'
: 'Please enter a valid phone number.'
myForm.reg_phone.setCustomValidity(msg)
}
// just to verify input...
myForm.onsubmit = evt =>
{
evt.preventDefault() // just for test here (disable submit)
// shwo values
console.clear()
console.log( 'name :' , myForm.reg_name.value )
console.log( 'address :' , myForm.reg_add.value )
console.log( 'Phone :' , myForm.reg_phone.value )
}
form {
display: flex;
flex-direction: column;
width: 18em;
}
label {
display: block;
margin-top: .7em;
}
label * {
vertical-align: baseline;
}
label input,
label textarea {
display: block;
float: right;
font-size:1.2em;
padding: .2em;
}
label input {
width: 9em;
}
button {
margin: 2em;
font-weight: bold;
}
<form action="..." name="my-form">
<label>
name :
<input type="text" name="reg_name" required>
</label>
<label>
address :
<textarea name="reg_add" rows="4" required placeholder="address"></textarea>
</label>
<label>
Phone :
<input type="text" name="reg_phone" required pattern="\d{3}-\d{4}-\d{4}" placeholder="___-____-____">
</label>
<button type="submit">Register</button>
</form>
I'm making a basic calculator and so far it "works". Users can only enter in a single digit from their keyboard. If they want to use double digits they're going to have to use the arrows in the text box. How can I make it so that users can enter in double digit numbers? If the user tries to type in double digit numbers the second number only get stored (so typing in 25 will only store 5)
// Base Variables
let result = 0;
let numb1 = 0;
let numb2 = 0;
let firstNumberEntered = false;
//This will be rewritten
let calculationDescription = `${numb1} + ${numb2}`;
document.querySelector('#input-number').addEventListener('keypress', numbersInput);
function numbersInput(e) {
if (!firstNumberEntered && e.key === 'Enter') {
numb1 = document.getElementById("input-number").value;
firstNumberEntered = true;
document.getElementById("input-number").innerHTML = "";
} else if (firstNumberEntered && e.key === 'Enter') {
numb2 = document.getElementById("input-number").value;
firstNumberEntered = false;
console.log(`${numb1} and ${numb2}`);
}
document.getElementById("input-number").value = "";
}
document.querySelector("#btn-add").addEventListener('click', sumNumbs);
document.querySelector("#btn-subtract").addEventListener('click', subtractNumbs);
document.querySelector("#btn-multiply").addEventListener('click', multiplyNumbs);
document.querySelector("#btn-divide").addEventListener('click', divideNumbs);
function sumNumbs() {
let numb1Final = parseInt(numb1);
let numb2Final = parseInt(numb2);
result = numb1Final + numb2Final;
calculationDescription = `${numb1} + ${numb2}`;
outputResult(result, calculationDescription);
}
function subtractNumbs() {
let numb1Final = parseInt(numb1);
let numb2Final = parseInt(numb2);
result = numb1Final - numb2Final;
calculationDescription = `${numb1} - ${numb2}`;
outputResult(result, calculationDescription);
}
function multiplyNumbs() {
let numb1Final = parseInt(numb1);
let numb2Final = parseInt(numb2);
result = numb1Final * numb2Final;
calculationDescription = `${numb1} x ${numb2}`;
outputResult(result, calculationDescription);
}
function divideNumbs() {
let numb1Final = parseInt(numb1);
let numb2Final = parseInt(numb2);
result = numb1Final / numb2Final;
calculationDescription = `${numb1} / ${numb2}`;
outputResult(result, calculationDescription);
}
<section id="calculator">
<input type="number" id="input-number" />
<div id="calc-actions">
<button type="button" id="btn-add">+</button>
<button type="button" id="btn-subtract">-</button>
<button type="button" id="btn-multiply">*</button>
<button type="button" id="btn-divide">/</button>
</div>
</section>
All this does is grab the elements on the HTML page and put them into variables and send info back to be displayed
const userInput = document.getElementById('input-number');
const addBtn = document.getElementById('btn-add');
const subtractBtn = document.getElementById('btn-subtract');
const multiplyBtn = document.getElementById('btn-multiply');
const divideBtn = document.getElementById('btn-divide');
const currentResultOutput = document.getElementById('current-result');
const currentCalculationOutput = document.getElementById('current-calculation');
function outputResult(result, text) {
currentResultOutput.textContent = result;
currentCalculationOutput.textContent = text;
}
Not sure if providing my CSS is necessary, but let me know and I'll make a change.
I just started learning JavaScript recently. I watched a few videos on a Udemy tutorial on doing this project, but I stopped watching and tried to figure the rest out myself.
What I did try doing was adding maxlength="6" to my HTML input element, but that didn't work.
function numbersInput(e) {
if(!firstNumberEntered && e.key === 'Enter') {
numb1 = document.getElementById("input-number").value;
firstNumberEntered = true;
document.getElementById("input-number").innerHTML = "";
document.getElementById("input-number").value = "";
}
else if(firstNumberEntered && e.key === 'Enter') {
numb2 = document.getElementById("input-number").value;
firstNumberEntered = false;
console.log(`${numb1} and ${numb2}`);
document.getElementById("input-number").value = "";
}
}
Add document.getElementById("input-number").value = "";
to the end of both if statements and remove from the end of the function
Here's my own basic calculator using HTML, CSS, and javascript only.
To be able to input one or more digit numbers, use arrays to function as storage of those numbers chosen and for your chosen arithmetic operation as well.
please see my source code for a clearer grasp of my idea.
//index.html
<!DOCTYPE html>
<html>
<head>
<title>My HTML/CSS/JS Calculator</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div class="outer-container">
<div class="items" id="items"></div>
<div class="calc-screen">
<div id="screen"></div>
</div>
<div class="keys-container">
<button type="button" class="keys" id="opm" onclick="opm()">*</button>
<button type="button" class="keys" id="opd" onclick="opd()">/</button>
<button type="button" class="keys" id="opa" onclick="opa()">+</button>
<button type="button" class="keys" id="ops" onclick="ops()">-</button>
<button type="button" class="keys" id="num9" onclick="num9()">9</button>
<button type="button" class="keys" id="num8" onclick="num8()">8</button>
<button type="button" class="keys" id="num7" onclick="num7()">7</button>
<button type="button" class="keys" id="num6" onclick="num6()">6</button>
<button type="button" class="keys" id="num5" onclick="num5()">5</button>
<button type="button" class="keys" id="num4" onclick="num4()">4</button>
<button type="button" class="keys" id="num3" onclick="num3()">3</button>
<button type="button" class="keys" id="num2" onclick="num2()">2</button>
<button type="button" class="keys" id="num1" onclick="num1()">1</button>
<button type="button" class="keys" id="num0" onclick="num0()">0</button>
<button type="button" class="keys" id="del" onclick="del()">Del</button>
<button type="button" class="keys" id="clr" onclick="clr()">Clr</button>
<button type="submit" class="keys" id="equals" onclick='equals()'>=</button>
</div>
</div>
<script type="text/javascript" src="js/js.js"></script>
</body>
</html>
//js.js
var item1, op, item2, arr1, arr2, text, x, prod, qout, sum, diff, a, b, c;
item1, item2, op = '';
arr1 = [];
arr2 = [];
function num9() {
if (op=='') {
arr1.push(document.getElementById("num9").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num9").innerHTML);
arr2Function();
}
}
function num8() {
if (op=='') {
arr1.push(document.getElementById("num8").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num8").innerHTML);
arr2Function();
}
}
function num7() {
if (op=='') {
arr1.push(document.getElementById("num7").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num7").innerHTML);
arr2Function();
}
}
function num6() {
if (op=='') {
arr1.push(document.getElementById("num6").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num6").innerHTML);
arr2Function();
}
}
function num5() {
if (op=='') {
arr1.push(document.getElementById("num5").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num5").innerHTML);
arr2Function();
}
}
function num4() {
if (op=='') {
arr1.push(document.getElementById("num4").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num4").innerHTML);
arr2Function();
}
}
function num3() {
if (op=='') {
arr1.push(document.getElementById("num3").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num3").innerHTML);
arr2Function();
}
}
function num2() {
if (op=='') {
arr1.push(document.getElementById("num2").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num2").innerHTML);
arr2Function();
}
}
function num1() {
if (op=='') {
arr1.push(document.getElementById("num1").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num1").innerHTML);
arr2Function();
}
}
function num0() {
if (op=='') {
arr1.push(document.getElementById("num0").innerHTML);
arr1Function();
}
else {
arr2.push(document.getElementById("num0").innerHTML);
arr2Function();
}
}
function opm() {
if (arr1=='') {
document.getElementById("screen").innerHTML = "Please press a number first!";
}
else if (op!='' && arr2!='') {
console.log("press equal sign");
}
else {
document.getElementById("screen").innerHTML = document.getElementById("opm").innerHTML;
op = '*';
console.log(op);
}
}
function opd() {
if (arr1=='') {
document.getElementById("screen").innerHTML = "Please press a number first!";
}
else if (op!='' && arr2!='') {
console.log("press equal sign");
}
else {
document.getElementById("screen").innerHTML = document.getElementById("opd").innerHTML;
op = '/';
console.log(op);
}
}
function opa() {
if (arr1=='') {
document.getElementById("screen").innerHTML = "Please press a number first!";
}
else if (op!='' && arr2!='') {
console.log("press equal sign");
}
else {
document.getElementById("screen").innerHTML = document.getElementById("opa").innerHTML;
op = '+';
console.log(op);
}
}
function ops() {
if (arr1=='') {
document.getElementById("screen").innerHTML = "Please press a number first!";
}
else if (op!='' && arr2!='') {
console.log("press equal sign");
}
else {
document.getElementById("screen").innerHTML = document.getElementById("ops").innerHTML;
op = '-';
console.log(op);
}
}
function equals() {
a = parseInt(item1); b = parseInt(item2);
if (op == '*') {
prod = a * b;
document.getElementById("items").innerHTML = a+' '+op+' '+b+' =';
c = prod;
document.getElementById("screen").innerHTML = c;
console.log('product: '+c);
result(c);
}
else if (op == '/') {
qout = a / b;
document.getElementById("items").innerHTML = a+' '+op+' '+b+' =';
c = qout;
document.getElementById("screen").innerHTML = c;
console.log('qoutient: '+c);
result(c);
}
else if (op == '+') {
sum = a + b;
document.getElementById("items").innerHTML = a+' '+op+' '+b+' =';
c = sum;
document.getElementById("screen").innerHTML = c;
console.log('sum: '+c);
result(c);
}
else if (op == '-') {
diff = a - b;
document.getElementById("items").innerHTML = a+' '+op+' '+b+ ' =';
c = diff;
document.getElementById("screen").innerHTML = c;
console.log('difference: '+c);
result(c);
}
else {
document.getElementById("screen").innerHTML = "Please press a number first!";
}
}
function result() {
console.log('function result: '+c);
arr1=[]; arr2=[]; item1, item2, op = '';
item1 = c;
console.log('function result new item1: '+item1);
arr1.push(item1);
arr1Function();
}
function del() {
if (arr1!='' && op=='' && arr2=='') {
arr1.pop();
console.log(arr1);
arr1Function();
}
else if (arr1!='' && op!='' && arr2=='') {
op = '';
document.getElementById("screen").innerHTML = op;
}
else if (arr1!='' && op!='' && arr2!='') {
arr2.pop();
console.log(arr2);
arr2Function();
}
}
function clr() {
arr1=[]; arr2=[]; item1,item2,op='';
console.log(arr1+', '+op+', '+arr2+', '+item1+', '+item2);
document.getElementById("screen").innerHTML = '';
document.getElementById("items").innerHTML = '';
}
function arr1Function() {
document.getElementById("screen").innerHTML = arr1;
text = ""; arr1.forEach(myFunction); text += "";
function myFunction(value) {
text += value;
x = parseInt(text);
document.getElementById("screen").innerHTML = x;
item1 = x;
console.log("Arr 1 Parse: "+x);
console.log("Arr 1: "+arr1);
console.log("Item 1: "+item1);
}
}
function arr2Function() {
document.getElementById("screen").innerHTML = arr2;
text = ""; arr2.forEach(myFunction); text += "";
function myFunction(value) {
text += value;
x = parseInt(text);
document.getElementById("screen").innerHTML = x;
item2 = x;
console.log("Arr 2 Parse: "+x);
console.log("Arr 2: "+arr2);
console.log("Item 2: "+item2);
}
}
//css
body {
background-color: orange;
}
.outer-container {
position: static;
background-color: black;
margin: auto;
border: solid black 2px;
width: 350px;
padding: 20px;
box-sizing: border-box;
border-radius: 20px;
}
.items {
background-color: silver;
border: solid white 1px;
display: inline-block;
color: black;
max-width: 100%;
font-size: 16px;
height: 20px;
box-sizing: border-box;
}
.calc-screen {
padding: 10px;
border: solid white 1px;
max-width: 100%;
width: 100%;
height: 50px;
background-color: silver;
color: white;
font-size: 25px;
box-sizing: border-box;
overflow-x: auto;
}
.keys-container {
margin-top: 20px;
width: 100%;
border: solid white 1px;
max-width: 100%;
display: inline-block;
}
#equals {
width: 100%;
}
.keys {
float: left;
border: solid black 1px;
box-sizing: border-box;
width: 25%;
height: 40px;
box-sizing: border-box;
text-align: center;
margin: auto;
font-size: 25px;
padding: 5px;
}
.keys:hover {
background-color: blue;
color: white;
}
I've been getting back into Javascript lately, and I've created this simple form validation as practice. I'm wondering what the best way to go about something, logically would be. Take a look at this code:
var regName = /^[A-Za-z]+$/;
var regEmail = /^[A-Za-z]+$/;
var regUserName = /^[A-Za-z]+$/;
var regPassword = /^[A-Za-z]+$/;
//span tags multiply when you keep hitting the submit button. .textContent doesn't seem to work, and even if it did it's kind of a cheap work around because the tags will still be there, you just wont see them. I need to check if the tag exists (before the if statement) and if it does, remove it.
var validate = {
validateName: function() {
var fName = document.getElementById('fName');
var nameContainer = document.querySelector('#nameContainer');
var infoSpan = document.createElement('span');
nameContainer.appendChild(infoSpan);
fName.classList.remove('errorBorder');
if (fName.value.match(regName)) {
console.log('Name Valid');
infoSpan.classList.add('checkmark');
infoSpan.innerHTML = '*checkmark*';
} else if(fName.value === '') {
console.log('input is empty');
} else {
console.log('Name Invalid');
fName.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.innerHTML = '*invalid input';
}
},
validateEmail: function() {
var fEmail = document.getElementById('fEmail');
var emailContainer = document.querySelector('#emailContainer');
var infoSpan = document.createElement('span');
emailContainer.appendChild(infoSpan);
fEmail.classList.remove('errorBorder');
if (fEmail.value.match(regEmail)) {
console.log('Email Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('Email Invalid');
fEmail.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
},
validateUserName: function() {
var fUserName = document.getElementById('fUserName');
var userNameContainer = document.querySelector('#userNameContainer');
var infoSpan = document.createElement('span');
userNameContainer.appendChild(infoSpan);
fUserName.classList.remove('errorBorder');
if (fUserName.value.match(regUserName)) {
console.log('User Name Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('User Name Invalid');
fUserName.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
},
validatePassword: function() {
var fPassword = document.getElementById('fPassword');
var passwordContainer = document.querySelector('#passwordContainer');
var infoSpan = document.createElement('span');
passwordContainer.appendChild(infoSpan);
fPassword.classList.remove('errorBorder');
if (fPassword.value.match(regPassword)) {
console.log('Passowrd Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('Passowrd Invalid');
fPassword.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
}
};
function onSubmit() {
validate.validateName();
validate.validateEmail();
validate.validateUserName();
validate.validatePassword();
}
<form onsubmit="return false">
<div id="nameContainer">
<label>Full Name: </label>
<input type="text" id="fName" />
</div>
<div id="emailContainer">
<label>Email: </label>
<input type="text" id="fEmail" />
</div>
<div id="userNameContainer">
<label>User Name: </label>
<input type="text" id="fUserName" />
</div>
<div id="passwordContainer">
<label>Password: </label>
<input type="password" id="fPassword" />
</div>
<br />
<input type="submit" value="submit" onclick="onSubmit()" />
</form>
If you take a look at my my code, you'll see there are 4 fields and a submit button. Right now the JavaScript code just returns either "checkmark" or "invalid input" inside tags next tot he input fields depending on if the input fields are populated or not.
The issues I'm having is, if you hit the submit button more than once (running all the functions agani), the
I would like only the most recent sets of span tags to show. Or in other words, before inserting the span tags, to check if they exist in the DOM and delete them if they do.
I updated the plaker and added a function that cleans all errors before submitting. That was missing in your code: https://plnkr.co/edit/tIvl5rplqAP53ylzLLtf?p=preview
function cleanErrors() {
var allErrors = document.querySelectorAll('form input + span');
for (var i=0; i < allErrors.length; i++) {
console.log('error',allErrors[i]);
allErrors[i].parentNode.removeChild(allErrors[i]);
}
}
function onSubmit() {
cleanErrors();
validate.validateName();
validate.validateEmail();
validate.validateUserName();
validate.validatePassword();
}
What I would do is assign the info div an ID, that way you can first try to get an existing infospan. Then check if it exists, if it does just use it instead, if it doesn't create it.
//var regName = /^[A-Za-z]+ [A-Za-z]+$/;
//var regEmail = /\w{1-20}#\w{1-10}.\w{2-3}/;
//var regUserName = /^[A-Za-z0-9_-]{1,20}$/;
//var regPassword = /^[A-Za-z0-9!##$%^&*()_-]{6,20}$/;
var regName = /^[A-Za-z]+$/;
var regEmail = /^[A-Za-z]+$/;
var regUserName = /^[A-Za-z]+$/;
var regPassword = /^[A-Za-z]+$/;
//span tags multiply when you keep hitting the submit button. .textContent doesn't seem to work, and even if it did it's kind of a cheap work around because the tags will still be there, you just wont see them. I need to check if the tag exists (before the if statement) and if it does, remove it.
var validate = {
validateName: function() {
var fName = document.getElementById('fName');
var nameContainer = document.querySelector('#nameContainer');
var infoSpan = document.getElementById('fNameInfo');
if (!infoSpan) {
infoSpan = document.createElement('span');
infoSpan.id = 'fNameInfo';
nameContainer.appendChild(infoSpan);
}
fName.classList.remove('errorBorder');
infoSpan.classList.remove('checkmark');
infoSpan.classList.remove('error');
if (fName.value.match(regName)) {
console.log('Name Valid');
infoSpan.classList.add('checkmark');
infoSpan.innerHTML = '*checkmark*';
} else if(fName.value === '') {
console.log('input is empty');
} else {
console.log('Name Invalid');
fName.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.innerHTML = '*invalid input';
}
},
validateEmail: function() {
var fEmail = document.getElementById('fEmail');
var emailContainer = document.querySelector('#emailContainer');
var infoSpan = document.getElementById('fEmailInfo');
if (!infoSpan) {
infoSpan = document.createElement('span');
infoSpan.id = 'fEmailInfo';
emailContainer.appendChild(infoSpan);
}
fEmail.classList.remove('errorBorder');
infoSpan.classList.remove('checkmark');
infoSpan.classList.remove('error');
if (fEmail.value.match(regEmail)) {
console.log('Email Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('Email Invalid');
fEmail.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
},
validateUserName: function() {
var fUserName = document.getElementById('fUserName');
var userNameContainer = document.querySelector('#userNameContainer');
var infoSpan = document.getElementById('fUserNameInfo');
if (!infoSpan) {
infoSpan = document.createElement('span');
infoSpan.id = 'fUserNameInfo';
userNameContainer.appendChild(infoSpan);
}
fUserName.classList.remove('errorBorder');
infoSpan.classList.remove('checkmark');
infoSpan.classList.remove('error');
if (fUserName.value.match(regUserName)) {
console.log('User Name Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('User Name Invalid');
fUserName.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
},
validatePassword: function() {
var fPassword = document.getElementById('fPassword');
var passwordContainer = document.querySelector('#passwordContainer');
var infoSpan = document.getElementById('fPasswordInfo');
if (!infoSpan) {
infoSpan = document.createElement('span');
infoSpan.id = 'fPasswordInfo';
passwordContainer.appendChild(infoSpan);
}
fPassword.classList.remove('errorBorder');
infoSpan.classList.remove('checkmark');
infoSpan.classList.remove('error');
if (fPassword.value.match(regPassword)) {
console.log('Passowrd Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('Passowrd Invalid');
fPassword.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
}
};
function onSubmit() {
validate.validateName();
validate.validateEmail();
validate.validateUserName();
validate.validatePassword();
}
/* Styles go here */
label, span {
font-family: sans-serif;
}
label {
font-size: 14px;
}
form div {
margin: 10px 0;
}
label {
display: inline-block;
width: 85px;
}
.errorBorder {
border: thin red solid;
}
span.error {
color: red;
font-size: 12px;
display: inline-block;
margin-left: 10px;
}
span.checkmark {
color: green;
font-weight: bold;
}
<form onsubmit="return false">
<div id="nameContainer">
<label>Full Name: </label>
<input type="text" id="fName" />
</div>
<div id="emailContainer">
<label>Email: </label>
<input type="text" id="fEmail" />
</div>
<div id="userNameContainer">
<label>User Name: </label>
<input type="text" id="fUserName" />
</div>
<div id="passwordContainer">
<label>Password: </label>
<input type="password" id="fPassword" />
</div>
<br />
<input type="submit" value="submit" onclick="onSubmit()" />
</form>
With the HTML structure that you have, the simplest way to know if there is a span element just after the current input element that is being validated is to just check the nextElementSibling of the input element to see if it is a span. If it is, then you need to remove the nextElementSibling and if not, you can add it.
For example, in your first validation:
var fName = document.getElementById('fName');
// .getElementById() is faster than .querySelector() when looking for id's
var nameContainer = document.getElementById('nameContainer');
// Is the next element sibling of the field a span?
if(nameContainer.nextElementSibling.nodeName === "SPAN"){
// span already exists - remove it
nameContainer.parentNode.removeChild(nameContainer.nextElementSibling);
} else {
// span does not exist - create and add it
var infoSpan = document.createElement('span');
nameContainer.appendChild(infoSpan);
}
Having said that, this is entirely unnecessary and hinders performance because you are modifying the DOM over and over.
Since you need a span no matter what, just create them statically in the HTML and then just update their textContent (don't use .innerHTML unless you have HTML to be parsed).
var regName = /^[A-Za-z]+$/;
var regEmail = /^[A-Za-z]+$/;
var regUserName = /^[A-Za-z]+$/;
var regPassword = /^[A-Za-z]+$/;
var validate = {
validateName: function() {
var fName = document.getElementById('fName');
var nameContainer = document.querySelector('#nameContainer');
var infoSpan = document.getElementById('nameInfo');
fName.classList.remove('errorBorder');
if (fName.value.match(regName)) {
console.log('Name Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else if(fName.value === '') {
console.log('input is empty');
} else {
console.log('Name Invalid');
fName.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
},
validateEmail: function() {
var fEmail = document.getElementById('fEmail');
var emailContainer = document.querySelector('#emailContainer');
var infoSpan = document.getElementById('emailInfo');
fEmail.classList.remove('errorBorder');
if (fEmail.value.match(regEmail)) {
console.log('Email Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('Email Invalid');
fEmail.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
},
validateUserName: function() {
var fUserName = document.getElementById('fUserName');
var userNameContainer = document.querySelector('#userNameContainer');
var infoSpan = document.getElementById('userNameInfo');
fUserName.classList.remove('errorBorder');
if (fUserName.value.match(regUserName)) {
console.log('User Name Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('User Name Invalid');
fUserName.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
},
validatePassword: function() {
var fPassword = document.getElementById('fPassword');
var passwordContainer = document.querySelector('#passwordContainer');
var infoSpan = document.getElementById('passwordInfo');
fPassword.classList.remove('errorBorder');
if (fPassword.value.match(regPassword)) {
console.log('Passowrd Valid');
infoSpan.classList.add('checkmark');
infoSpan.textContent = '*checkmark*';
} else {
console.log('Passowrd Invalid');
fPassword.classList.add('errorBorder');
infoSpan.classList.add('error');
infoSpan.textContent = '*invalid input';
}
}
};
function onSubmit() {
validate.validateName();
validate.validateEmail();
validate.validateUserName();
validate.validatePassword();
}
/* Styles go here */
label, span {
font-family: sans-serif;
}
label {
font-size: 14px;
}
form div {
margin: 10px 0;
}
label {
display: inline-block;
width: 85px;
}
.errorBorder {
border: thin red solid;
}
span.error {
color: red;
font-size: 12px;
display: inline-block;
margin-left: 10px;
}
span.checkmark {
color: green;
font-weight: bold;
}
<form onsubmit="return false">
<div id="nameContainer">
<label>Full Name: </label>
<input type="text" id="fName"><span id="nameInfo"></span>
</div>
<div id="emailContainer">
<label>Email: </label>
<input type="text" id="fEmail"><span id="emailInfo"></span>
</div>
<div id="userNameContainer">
<label>User Name: </label>
<input type="text" id="fUserName"><span id="userNameInfo"></span>
</div>
<div id="passwordContainer">
<label>Password: </label>
<input type="password" id="fPassword"><span id="passwordInfo"></span>
</div>
<br />
<input type="submit" value="submit" onclick="onSubmit()">
</form>
You may also want to reconsider this entire design given that each of your validation functions is essentially the same, except for the regular expression that it uses to determine validity. Additionally, you could also just use the HTML5 form fields that have built-in validation or a full API for you to work from.
// Do all event wiring in JavaScript, not with HTML event attributes
// And, form validation should be done on the form's submit event and
// the form submission should be cancelled via the event object that
// is automatically passed to the function
document.querySelector("form").addEventListener("submit", function(evt){
// Store regular expressions in an object
var regs = {
regName : /^[A-Za-z]+$/,
regEmail : /^[A-Za-z]+$/,
regUserName : /^[A-Za-z]+$/,
regPassword : /^[A-Za-z]+$/
};
var valid = true; // Set up variable to track any errors
// Gather up all the fields to be validated into an array.
var fieldsToValidate = Array.prototype.slice.call(document.querySelectorAll(".validate"));
// Loop over each of the fields to be validated
fieldsToValidate.forEach(function(field, index){
var message = "";
var infoSpan = field.nextElementSibling;
// Test the field against the corresponding regular expression in the object
if(field.value.match(regs[Object.keys(regs)[index]])){
message = "*checkmark*";
infoSpan.classList.remove("error");
infoSpan.classList.add("checkmark");
} else {
valid = false;
message = "*error*";
infoSpan.classList.remove("checkmark");
infoSpan.classList.add("error");
}
infoSpan.textContent = message;
});
// If there were any errors, prevent the form from being submitted
if(!valid){ evt.preventDefault(); }
});
label, span { font-family: sans-serif; }
label { font-size: 14px; display: inline-block; width: 85px; }
form div { margin: 10px 0; }
.error {
color: red;
font-size: 12px;
display: inline-block;
margin-left: 10px;
border: thin red solid;
}
.checkmark {
color: green;
font-weight: bold;
}
<form>
<div>
<label>Full Name: </label>
<input type="text" id="fName" class="validate"><span id="nameInfo"></span>
</div>
<div>
<label>Email: </label>
<input type="text" id="fEmail" class="validate"><span id="emailInfo"></span>
</div>
<div>
<label>User Name: </label>
<input type="text" id="fUserName" class="validate"><span id="userNameInfo"></span>
</div>
<div>
<label>Password: </label>
<input type="password" id="fPassword" class="validate"><span id="passwordInfo"></span>
</div>
<div><input type="submit" value="submit"></div>
</form>