How to validate an single/multiple image in Javascript and show error message below the input with prevent default submit? - javascript

Actually I have a form where with image input. I want to validate image for three condition like
extension should be png, jpg
size should be less than 2048kb
less than 200px x 200px is consider as dimension
I wrote an function and solve 1 and 2 issue. To solve issue 3 , I use image reader inside onload listener and when I clicked it can not prevent submit also if I remove 3, then it works fine ! Is there any solution in JS that solve above issue?
Here is a slide of my code in below.
function isImageValid(idName) {
var fileUpload = document.getElementById(idName);
var fileName = document.getElementById(idName).value;
if (typeof (fileUpload.files) != "undefined") {
for (var i=0; i<fileUpload.files.length;i++)
{
// console.log(fileUpload.files[i]);
var valid_dimension = 0;
var reader = new FileReader();
//Read the contents of Image File.
reader.readAsDataURL(fileUpload.files[0]);
reader.onload = function (e) {
//Initiate the JavaScript Image object.
var image = new Image();
//Set the Base64 string return from FileReader as source.
image.src = e.target.result;
//Validate the File Height and Width.
image.onload = function () {
var height = this.height;
var width = this.width;
if (height>200||width>200) {
valid_dimension =1;
// alert("Height and Width must not exceed 200px.");
return false;
}
// alert("Uploaded image has valid Height and Width.");
return true;
};
};
var size = parseFloat(fileUpload.files[0].size / 1024).toFixed(2);
var extension = fileName.split('.').pop();
if( valid_dimension ==1||size>2048||(extension!='jpg'&&extension!='JPG'&&extension!='JPEG'&&extension!='jpeg'&&extension!='png'&&extension!='PNG'))
return false;
else
return true;
}
} else {
return false;
}
}
And,
const form = document.getElementById('employee_form');
form.addEventListener('submit', (e)=>{
var is_avatar_img_valid = isImageValid('avatar');
if(is_avatar_img_valid==false){
e.preventDefault();
document.getElementById("avatar").style.borderColor = "red";
document.getElementById('avatar_validator_message').innerHTML = 'Invalid image';
}
else{
document.getElementById("avatar").style.borderColor = "black";
document.getElementById('avatar_validator_message').innerHTML = '';
}
}

The problem is reader.onload and image.onload functions are async in nature. So your form submits before these onload methods execute.
To solve this you need to follow the below steps
Prevent default in submit event handler
Pass callbacks for valid and invalid image to the isImageValid function
Manually submit the form if image is valid
Below is the code. Please mark the answer as accepted, if it helps
function isImageValid(idName, onValidCallback, onInvalidCallback) {
var fileUpload = document.getElementById(idName);
var fileName = document.getElementById(idName).value;
if (typeof (fileUpload.files) != "undefined") {
for (var i = 0; i < fileUpload.files.length; i++) {
// console.log(fileUpload.files[i]);
//--------------------
const allowedExtension = ['jpg', 'JPG', 'JPEG', 'jpeg', 'png', 'PNG'];
const maxAllowedSize = 2048;
const maxAllowedHeight = 200;
const maxAllowedWidth = 200;
const size = parseFloat(fileUpload.files[i].size / 1024).toFixed(2);
const extension = fileName.split('.').pop();
console.log({ size, extension });
//Check for valid extension and size limit
if (allowedExtension.some(ext => ext === extension) && size <= maxAllowedSize) {
//Extension and size are valid
// Now check for valid dimensions
const reader = new FileReader();
reader.readAsDataURL(fileUpload.files[i]);
reader.onload = function (e) {
//Initiate the JavaScript Image object.
var image = new Image();
//Set the Base64 string return from FileReader as source.
image.src = e.target.result;
//Validate the File Height and Width.
image.onload = function () {
const height = this.height;
const width = this.width;
console.log({ height, width });
if (height > maxAllowedHeight || width > maxAllowedWidth) {
// alert("Height and Width must not exceed 200px.");
//File does not meet the dimensions guidline
if (onInvalidCallback)
onInvalidCallback();
return false;
}
// alert("Uploaded image has valid Height and Width.");
//Everything is fine, form canbe submitted now
if (onValidCallback)
onValidCallback();
};
};
}
break;
}
}
else {
// There are no files selected
if (onInvalidCallback)
onInvalidCallback();
}
}
const form = document.getElementById('employee_form');
form.addEventListener('submit', (e) => {
e.preventDefault();
isImageValid('avatar', () => {
alert('going to submit');
document.getElementById("avatar").style.borderColor = "black";
document.getElementById('avatar_validator_message').innerHTML = '';
//Manually submit the form
form.submit();
},
() => {
alert('stop submit');
document.getElementById("avatar").style.borderColor = "red";
document.getElementById('avatar_validator_message').innerHTML = 'Invalid image';
}
);
return false;
});

Inside the form eventlister you need to use
e.preventDefault()
The full code looks like this
const form = document.getElementById('employee_form');
form.addEventListener('submit', (e) => {
e.preventDefault()
var is_avatar_img_valid = isImageValid('avatar');
if (is_avatar_img_valid == false) {
e.preventDefault();
document.getElementById("avatar").style.borderColor = "red";
document.getElementById('avatar_validator_message').innerHTML = 'Invalid image';
}
else {
document.getElementById("avatar").style.borderColor = "black";
document.getElementById('avatar_validator_message').innerHTML = '';
}
});

Related

Validation on Uploaded Image Size using Angular 5

I'm currently working on an angular web app, and of the features is the photo upload.
I would like to implement validation on the image size so that I can throw errors if the image is too small.
Here is my code:
public onImageDrop(evt: any) {
evt.stopPropagation();
evt.preventDefault();
this.croppieImage = null;
this.onCropeMode = true;
const image: HTMLImageElement = new Image();
const file: File = evt.dataTransfer.files[0];
const myReader: FileReader = new FileReader();
myReader.onloadend = ((loadEvent: any) => {
image.src = loadEvent.target.result;
this.croppieImage = myReader.result;
});
myReader.readAsDataURL(file);
**console.log(image.height);
console.log(image.width);**
this.photoInDragMode = false;
this.uplodedPhotoFileName = file.name;
this.uplodedPhotoFileMimeType = file.type;
this.showPhotoSaveButton = true;
this.onCropeMode = true;
}
The problem I have is that the
console.log(image.height);
console.log(image.width);
Always shows me
> 0
> 0
I really appreciate if anyone can help.
Thanks in advance guys.
HTML
<input type='file'
formControlName="img_name"
class="form-control"
(change)="readUrl($event)">
TS
readUrl(event: any) {
if (event.target.files && event.target.files[0]) {
if (event.target.files[0].type === 'image/jpeg' ||
event.target.files[0].type === 'image/png' ||
event.target.files[0].type ==='image/jpg') {
if (event.target.files[0].size < 200 * 200) {/* Checking height * width*/ }
if (event.target.files[0].size < 2000000) {/* checking size here - 2MB */ }
}
}
You get 0 because the image was not loaded yet when you place the console logs. It's loaded in
myReader.onloadend = ((loadEvent: any) => {
image.src = loadEvent.target.result;
this.croppieImage = myReader.result;
});
so you can do something like
myReader.onloadend = ((loadEvent: any) => {
image.src = loadEvent.target.result;
this.croppieImage = myReader.result;
});
image.onload = function(){
// image has been loaded
console.log(image.height);
};
Try This
reader.onload = (event) => {
var img = new Image;
this.url = event.target.result;
img.src = event.target.result;
console.log(img.width);
}
Example:https://stackblitz.com/edit/angular-file-upload-preview-yxuayc

How can I make return works in load function on vue component?

I have vue component like this :
<template>
<section>
...
</section>
</template>
<script>
export default {
...
data() {
return {
allowableTypes: ['jpg', 'jpeg', 'png'],
maximumSize: 4000000
}
},
methods: {
...
onFileChange(e) {
if (this.validate(e.target.files[0])) {
let files = e.target.files,
reader = new FileReader()
// if any values
if (files.length){
reader.onload = (e) => {
this.image = e.target.result
}
reader.readAsDataURL(files[0])
}
}
},
validate(image) {
// validation file type
if (!this.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
return false
}
// validation file size
if (image.size > this.maximumSize) {
return false
}
// validation image resolution
let img = new Image()
img.src = window.URL.createObjectURL(image)
let self = this
img.onload = function() {
let width = img.naturalWidth,
height = img.naturalHeight
window.URL.revokeObjectURL(img.src)
if(width != 850 && height != 350) {
return false
}
}
return true
}
}
}
</script>
If user upload image, it will call onFileChange method. Before displaying the image, it will call method validate to validate the image.
I try to validate file size and file type and it works. The problem here is validating the resolution.
From my code, it seems my code is true
But when I try like this:
I upload image with width = 100, height = 100, from the code, should it return `false``.
But when I run my code, it returns true.
Seems return is not working in the img.onload
How can I solve this problem?
A nice way to handle asynchronous validation is by using Promises :
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
If you are targeting Internet Explorer, make sure to use a polyfill such as this one :
https://github.com/stefanpenner/es6-promise
Your code would then look like this :
onFileChange(e) {
let self = this
this.validate(e.target.files[0])
.then(function() {
let files = e.target.files,
reader = new FileReader()
// if any values
if (files.length) {
reader.onload = (e) => {
self.image = e.target.result
}
reader.readAsDataURL(files[0])
}
})
.catch(function() {
// do something in the case where the image is not valid
})
},
validate(image) {
let self = this
return new Promise(function(resolve, reject) {
// validation file type
if (!self.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
reject()
}
// validation file size
if (image.size > self.maximumSize) {
reject()
}
// validation image resolution
let img = new Image()
img.src = window.URL.createObjectURL(image)
img.onload = function() {
let width = img.naturalWidth,
height = img.naturalHeight
window.URL.revokeObjectURL(img.src)
if (width != 850 && height != 350) {
reject()
} else {
resolve()
}
}
})
}
If you do not want or cannot use Promises you could use a Callback to achieve the same behaviour :
onFileChange(e) {
let self = this
let validCallback = function() {
let files = e.target.files,
reader = new FileReader()
// if any values
if (files.length) {
reader.onload = (e) => {
self.image = e.target.result
}
reader.readAsDataURL(files[0])
}
}
let unvalidCallback = function() {
// do something in the case where the image is not valid
}
this.validate(e.target.files[0], validCallback, unvalidCallback)
},
validate(image, validCallback, unvalidCallback) {
// validation file type
if (!this.allowableTypes.includes(image.name.split(".").pop().toLowerCase())) {
unvalidCallback()
return
}
// validation file size
if (image.size > this.maximumSize) {
unvalidCallback()
return
}
// validation image resolution
let img = new Image()
img.src = window.URL.createObjectURL(image)
let self = this
img.onload = function() {
let width = img.naturalWidth,
height = img.naturalHeight
window.URL.revokeObjectURL(img.src)
if (width != 850 && height != 350) {
unvalidCallback()
return
} else {
validCallback()
}
}
}
It's onloadend not onload.
Change your code to this:
let self = this;
var reader = new FileReader();
reader.onloadend = () => {
// you logic here (use self, not this)
}

Async checking image width and height and if they are OK, preview image

I'm trying to check width and height from an input file's images and check if they are at least equal than specific dimension (w:300px, h:300px).
I have this check:
window.onload = function () {
var fileUpload = document.getElementById("inputFileID");
fileUpload.onchange = function () {
if (typeof (FileReader) != "undefined") {
var dvPreview = document.getElementById("divToShowThumbs");
dvPreview.innerHTML = "";
var regex = /^([a-zA-Z0-9\s_\\.\-:])+(.jpg|.jpeg|.gif|.png|.bmp)$/;
for (var i = 0; i < fileUpload.files.length; i++) {
var file = fileUpload.files[i];
if (regex.test(file.name.toLowerCase())) {
var reader = new FileReader();
reader.onload = function (e) {
var img = document.createElement("IMG");
img.src = e.target.result;
dvPreview.appendChild(img);
}
reader.readAsDataURL(file);
} else {
alert(file.name + " is not a valid image file.");
dvPreview.innerHTML = "";
return false;
}
}
} else {
alert("This browser does not support HTML5 FileReader.");
}
}
};
This works ok to preview each image.
But when I try to use "IF - ELSE" using img.width, it returns 0 because it works in asynchronous way!
Any light about how can I solve this situation?
All I'm trying to do is read each image, check if they area 300px (height and width) and if Ok, create the preview!
I got help from this - Getting width & height of an image with filereader . And here is your modified script.
window.onload = function () {
var fileUpload = document.getElementById("inputFileID");
fileUpload.onchange = function () {
if (typeof (FileReader) != "undefined") {
var dvPreview = document.getElementById("divToShowThumbs");
dvPreview.innerHTML = "";
var regex = /^([a-zA-Z0-9\s_\\.\-:])+(.jpg|.jpeg|.gif|.png|.bmp)$/;
for (var i = 0; i < fileUpload.files.length; i++) {
var file = fileUpload.files[i];
if (regex.test(file.name.toLowerCase())) {
var reader = new FileReader();
reader.onload = function (e) {
var img = document.createElement("IMG");
img.src = e.target.result;
img.onload = function() {
// access image size here
console.log(this.width + " " + this.height);
if(this.width <=300 && this.height <=300 ) {
dvPreview.appendChild(img);
}
else {
alert("too big an image!");
}
};
};
reader.readAsDataURL(file);
} else {
alert(file.name + " is not a valid image file.");
dvPreview.innerHTML = "";
return false;
}
}
} else {
alert("This browser does not support HTML5 FileReader.");
}
};
};

checking image dimensions before ajax post event in javascript

I am attempting to access the dimensions from my uploaded image before I post it to the post upload endpoint. If you look, there is a variable issueArrived that is supposed to determine if the image is too small and if so negate the upload process. Unfortunately, the image doesn't access the dimensions check until after the upload to the server commences. Any help would be appreciated.
// internal function that creates an input element
var file = newElement("input", 0, "");
// sets the input element as type file
file.type = "file";
file.multiple = "multiple";
file.name = "photoupload[]";
file.accept = "image/*";
var issueArrived = false;
file.onchange = function(e) {
// internal function that creates an image element
var img = newElement("img", 0, "");
img.onload = function(){
var cw = img.width;
var ch = img.height;
if(cw < 500 || ch < 500) {
alert("Photo must be atleast 500x500");
issueArrived = true;
return false;
}
}
img.src = URL.createObjectURL(this);
if (issueArrived) return;
var formdata = new FormData();
formdata.append("photoupload[]", this);
var x = new XMLHttpRequest();
x.open("POST", "/media/uploadphotos", true);
x.send(formdata);
}
file.click();
onchange method is async. You can't expect to set the value from issueArrived inside it an access it outside.
You can rewrite this way:
file.onchange=function(e){
// internal function that creates an image element
var img=newElement("img",0,"");
img.onload=function(){
var cw=img.width;
var ch=img.height;
if(cw<500||ch<500){
alert("Photo must be atleast 500x500");
} else {
var formdata=new FormData();
formdata.append("photoupload[]",this);
var x=new XMLHttpRequest();
x.open("POST","/media/uploadphotos",true);
x.send(formdata);
}
}
img.src=URL.createObjectURL(this);
}
file.click();
or, better, you can create a function that gets the file and returns a promise.
function checkSize(fileToCheck) {
return new Promise( function(resolve, reject) {
var img = newElement("img",0,"");
img.onload=function(){
var cw=img.width;
var ch=img.height;
cw<500||ch<500 ? reject() : resolve()
}
img.src=URL.createObjectURL(fileToCheck);
})
}
...
file.onchange=function(e){
checkSize(this)
.then( function() { /* success handler */ } )
.catch( function() { /* error handler */ } )
}

Listen for the statement until it's true

I'm looking for the solution to my problem with image upload. I have one script that is kind of compressing my files with Javascript. Then it creates an Array with results in base64 which I want to add to my page as an <input type="hidden">. I have just one problem. It takes a bit to compress these images and .change() event on file input is not enough. Because it begins before the compression is done.
I was thinking about adding something like event listener for the statement, something like:
if(result_base64.length != input.files.length){
listen
} else {
do the function
}
Is it possible to achieve that without setting the interval function?
The compression script:
var result_base64 = [];
var images = document.getElementById('images');
var max_width = images.getAttribute('data-maxwidth');
var max_height = images.getAttribute('data-maxheight');
images.onchange = function(){
if ( !( window.File && window.FileReader && window.FileList && window.Blob ) ) {
alert('The File APIs are not fully supported in this browser.');
return false;
}
readfiles(images.files);
}
function readfiles(files) {
for (var i = 0; i < files.length; i++) {
processfile(files[i]);
}
images.value = "";
}
function processfile(file) {
if( !( /image/i ).test( file.type ) ){
alert( "File "+ file.name +" is not an image." );
return false;
}
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (event) {
var blob = new Blob([event.target.result]);
window.URL = window.URL || window.webkitURL;
var blobURL = window.URL.createObjectURL(blob);
var image = new Image();
image.src = blobURL;
image.onload = function() {
result_base64.push(resizeMe(image));
}
};
}
function resizeMe(img) {
var canvas = document.createElement('canvas');
var width = img.width;
var height = img.height;
if (width > height) {
if (width > max_width) {
height = Math.round(height *= max_width / width);
width = max_width;
}
} else {
if (height > max_height) {
width = Math.round(width *= max_height / height);
height = max_height;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
return canvas.toDataURL("image/jpeg",0.7);
}
Then it creates an Array with results in base64
In processFile() you can fire an event like
function processfile(file) {
if( !( /image/i ).test( file.type ) ){
alert( "File "+ file.name +" is not an image." );
return false;
}
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (event) {
var blob = new Blob([event.target.result]);
window.URL = window.URL || window.webkitURL;
var blobURL = window.URL.createObjectURL(blob);
var image = new Image();
image.src = blobURL;
image.onload = function() {
result_base64.push(resizeMe(image));
$(window).trigger('filesCompressed', result_base64); // This is the addition
}
};
}
Where you need these files, you can listen for this event like
$(window).on('filesCompressed', function(e, files) {
// Do something with files
})
Catch: The listening code must execute before the event is triggered.

Categories

Resources