6

I have a form where validation occurs in jQuery's validation plugin. My form contains two elements, an input type=file and a submit button. The user will select an image, and if that image is less than 500px, it won't be accepted and an error message should be shown. I have made a new method for this called width, but it's not working for some reason. The error message is not shown when I try to submit an image less than 500px. Here's my jsfiddle.

HTML

<form class="some_form" enctype="multipart/form-data" method="post" action="">
  <input type="file" name="photo" id="photoInput" />
  <input type="submit">
</form>

jQuery

$.validator.addMethod('width', function(value, element) {
  if ($(element).data('width')) {
    return $(element).data('width') >= 500;
  }

  return true;
}, 'Image needs to be wider than 500px');

$(".some_form").validate({
  rules: {
    photo: {
      required: true,
      width: 500
    }

  },

  messages: {
    photo: {
      required: "You must insert an image",
      width: "Your image's width must be greater than 500px"
    },
  }

});
user2896120
  • 2,563
  • 2
  • 27
  • 67
  • Image width doesn't magically appear in element data by itself. You need to process the image file yourself first – charlietfl Jul 16 '16 at 04:31
  • @charlietfl How would I do that? – user2896120 Jul 16 '16 at 04:36
  • need to use `FileReader` API – charlietfl Jul 16 '16 at 04:37
  • @charlietfl How do I combine the usage of FileReader with the jQuery validation plugin though? – user2896120 Jul 16 '16 at 04:40
  • Will need to create image element...use filreader to generate it's `src` then use load event to check dimensions – charlietfl Jul 16 '16 at 04:42
  • @charlietfl I generate the src inside the addMethod 'width' function? – user2896120 Jul 16 '16 at 04:45
  • As, if I am right then as @charlietfl suggested, you have to get source of image file and set it into img tag then you can get its width. – Kirankumar Dafda Jul 16 '16 at 04:49
  • See http://stackoverflow.com/a/13572209/1344961 – SLePort Jul 16 '16 at 04:52
  • @KirankumarDafda Do I do this inside the addMethod width function? – user2896120 Jul 16 '16 at 04:57
  • @user2896120, May be you can try using onchange function of file element and get that value into an image element on call that onchange function. – Kirankumar Dafda Jul 16 '16 at 05:00
  • @KirankumarDafda Where do I call the onchange function? – user2896120 Jul 16 '16 at 05:01
  • 1
    I added to your [jsfiddle](https://jsfiddle.net/3fr8s9x7/3/) to get you a bit further along. It now loads the selected image into a new `` element. You would now have to tie that into the JQuery Validator method. – John S Jul 16 '16 at 05:03
  • This is what i was exactly trying to explain as @JohnS updated in your jsfiddle – Kirankumar Dafda Jul 16 '16 at 05:07
  • @JohnS I understand how to get the width using FileReader, but I don't understand how I can tie that into the jQuery validator method – user2896120 Jul 16 '16 at 05:10
  • @KirankumarDafda Do you know how I can tie jQuery validator method with the filereader? – user2896120 Jul 16 '16 at 05:16
  • Yeah, I think I helped with the easy part and left you with the hard part. Hopefully it is possible. You basically need to add an asynchonous method, but so far all I can see is that JQuery Validator has a `remote` method that supports ajax calls. – John S Jul 16 '16 at 05:16
  • @JohnS Hmm, do you think it would be easier to just validate using the FileReader and Javascript, and not use the jQuery plugin? – user2896120 Jul 16 '16 at 05:17
  • I was over-thinking it. I was thinking the method had to be asynchronous, but you can just disable the submit button while you wait for the image to load. Check out [this jsfiddle](https://jsfiddle.net/3fr8s9x7/7/). – John S Jul 16 '16 at 06:25
  • @JohnS Works perfectly! Is there anyway to change the height of the image that's loaded on to the HTML? I tried using CSS to change the #imgContainer but it didn't work – user2896120 Jul 16 '16 at 06:42
  • @JohnS Nevermind! Made it work. Had to add #imgContainer img. Is there anyway for the image to not load if the user uploaded an image smaller than 500px? – user2896120 Jul 16 '16 at 06:46
  • [Here's a version](https://jsfiddle.net/3fr8s9x7/9/) where I made the method generic and named it "callbackTest". I also made the container hidden, so you don't actually see the image. – John S Jul 16 '16 at 06:48
  • @JohnS Hmm, with this version I can't upload images that are greater than 500px. When I do, it displays an error message saying I have to upload an image that is greater than 500px – user2896120 Jul 16 '16 at 06:51
  • Sorry, I swear that version worked before I added the comment. Try [this version](https://jsfiddle.net/3fr8s9x7/11/). As I suspected, you can't get the width of the `` element until it has been added to the DOM and is visible, but you can hide it right away if it is not 500px wide. – John S Jul 16 '16 at 07:06
  • @JohnS Works like a charm! What's weird is that when I tried to change the CSS height of an image that in file is 1048px, to a CSS height of 250px, the validation says the image is less than 500px wide. Does this only get the height of the image that's on the page and not the height of the actual image file? If so are there any work arounds to it? – user2896120 Jul 16 '16 at 07:11
  • I think its because it is getting the height of the `` element, not the height of the actual image. You could always change the image size after you get its width. That will cause a bit of a flash, but you can minimize that by setting the container size with the overflow hidden, [like this](https://jsfiddle.net/3fr8s9x7/13/). – John S Jul 16 '16 at 07:33
  • @JohnS What do you mean by bit of a flash? – user2896120 Jul 16 '16 at 07:38
  • When I tried it without the clipping, the image would flash as its full size and then display at the smaller size. Just delete the CSS in the fiddle and try it. Perhaps it won't flash for you. It may depend on the browser. – John S Jul 16 '16 at 07:41
  • @JohnS Hmm it doesn't do that with me, I'm using Chrome. Are you using anything different if I may ask? – user2896120 Jul 16 '16 at 07:45
  • I am using Firefox, but I just tried it with Chrome & IE, and there was no flash with either of them. With those two, the image does not display until after its size has been reduced. – John S Jul 16 '16 at 07:51
  • @JohnS Does the flashing happen in the blink of an eye? Or is it slow? – user2896120 Jul 16 '16 at 07:54
  • If the image is large, its a bit more than a blink, but I updated my answer so its gone. Its not necessary to add the new `` element to the DOM until after it is loaded, so I moved that line of code. – John S Jul 16 '16 at 17:26

1 Answers1

7

You can load the image file into an <img> element, and then get the width of that element.


First add a container element to your HTML to hold the <img> element:

<form class="some_form" enctype="multipart/form-data" method="post" action="">
  <input type="file" name="photo" id="photoInput" />
  <input type="submit">
</form>
<div id="imgContainer"></div>

Next, define a custom Validator method:

$.validator.addMethod('minImageWidth', function(value, element, minWidth) {
  return ($(element).data('imageWidth') || 0) > minWidth;
}, function(minWidth, element) {
  var imageWidth = $(element).data('imageWidth');
  return (imageWidth)
      ? ("Your image's width must be greater than " + minWidth + "px")
      : "Selected file is not an image.";
});

Notes:

  1. As you can see, the method expects that it can get the image width by calling .data('imageWidth') on the file input element. We will be setting that value in code shown below.
  2. Also, the method assumes that when .data('imageWidth') returns undefined, the selected file is not an image.

You can now use the minImageWidth method like this:

var validator = $('.some_form').validate({
  rules: {
    photo: {
      required: true,
      minImageWidth: 500
    }
  },
  messages: {
    photo: {
      required: "You must insert an image"
    },
  }
});

Finally, add a change-event handler for the file input that creates the <img> element and adds it to the container. It will also set the .data('imageWidth') value on the file input element.

var $submitBtn = $('.some_form').find('input:submit'),
  $photoInput = $('#photoInput'),
  $imgContainer = $('#imgContainer');

$('#photoInput').change(function() {
  $photoInput.removeData('imageWidth');
  $imgContainer.hide().empty();

  var file = this.files[0];

  if (file.type.match(/image\/.*/)) {
    $submitBtn.attr('disabled', true);

    var reader = new FileReader();

    reader.onload = function() {
      var $img = $('<img />').attr({ src: reader.result });

      $img.on('load', function() {
        $imgContainer.append($img).show();
        var imageWidth = $img.width();
        $photoInput.data('imageWidth', imageWidth);
        if (imageWidth < 500) {
          $imgContainer.hide();
        } else {
          $img.css({ width: '400px', height: '200px' });
        }
        $submitBtn.attr('disabled', false);

        validator.element($photoInput);
      });
    }

    reader.readAsDataURL(file);
  } else {
    validator.element($photoInput);
  }
});

Notes:

  1. When the image is loaded, its width is placed as a .data('imageWidth') value on the file input element.
  2. While the image is loading, the submit button is disabled so the user cannot submit the form.
  3. The validation is performed immediately after the image is loaded. That is, the user does not have to click the submit button in order to for the error message to show up. This is accomplished by the call to validator.element().
  4. You cannot get the width of the <img> element until it is added to the DOM. It must also be visible, but the code above shows how it can be hidden right after if desired.

jsfiddle


References:

Community
  • 1
  • 1
John S
  • 20,117
  • 7
  • 39
  • 52
  • Great answer! Thanks a lot! – user2896120 Jul 16 '16 at 07:26
  • @user2896120 - I made some improvements to my answer: (1) It now shows a different error message if the selected file is not an image, and (2) The validation is now performed immediately after the file is selected. – John S Jul 16 '16 at 18:51
  • Looks great and works great as well! If I were to add height validation, would I need to add another addMethod()? – user2896120 Jul 16 '16 at 19:35
  • @user2896120 - You could go either way. You could add a `minImageHeight` method, or you could change the `minImageWidth` method into a `minImageSize` method, [like this](https://jsfiddle.net/3fr8s9x7/23/). – John S Jul 16 '16 at 19:55
  • I have a form where it's dynamically cloned multiple times. Is there a way to make each form unique with its own image? To not confuse you, here's another question I have here with the code you provided to me, used in my work: (http://stackoverflow.com/questions/38416454/jquery-image-validation-for-dynamically-cloned-form/38416907) – user2896120 Jul 17 '16 at 03:00