Category Archives: HTML, CSS, JavaScript (JQuery, AJAX)

JavaScript form validation for file-uploads–enforcing the extension to be in an approved list.

Building on my last post, here is some new html form-checking functionality, this time for enforcing file uploads to have certain extensions. For example, when uploading a profile picture, you may want to restrict the file-type to be “.gif”, “.jpg”, or “.png”. Although this is no substitution for checking on the server (such as restricting a maximum file size in the nginx configuration), having some client-side protection is a nice supplement.

This function accepts an array of file-types. The file’s name must be one of those types, or a red message is presented, and the submit button is optionally disabled. This required a reorganization of the check-password code–and it’s now-not private function. They are in three separate files, as listed below.

(Known issue: The disable-submit button won’t behave properly if you tell both checkFileExtension and passwordsMatchAlert to disable/enable it. If the current field is valid, the button will be enabled. Same with disabling. While you won’t be prevented from submitting a totally valid form, it means the button will be made pressable if only the current field is valid.)

check_file_extension.js

/**
   Enforces the name of an uploaded file to have a valid extension, as
   contained in an array of extension types. If the extension is
   invalid, the submit button may be optionally disabled.

   This function requries
   `disable_submit_button.disableSubmitButtonIfIdNonNull`

   Derived from (downloaded 8/29/2014)
      http://stackoverflow.com/a/5796710/2736496

   @param  {string[]}  allowed_extensionArrayNoDot  An array of
   extension types, not including the dot prefix. For example:
   ["gif", "jpg", "png"]. *Should* contain unique, non-`null`, and
   non-empty values.
   @param  {string}  message_spanID  ID of the span in which to display
   the message. If `null`, defaults to "message_span_id".
   @param  {string}  submit_buttonIdNullIfDontDisable  If the submit
   button should be disabled unless the passwords match. See
   `disable_submit_button.disableSubmitButtonIfIdNonNull`.
   @param  {string}  message_spanId  ID of the span in which to display
   the "do not match!" message. If null, defaults to "message_span_id".
   @param  {string}  bad_color  The hex color for both the message and
   file-upload element, when the extension is bad. If `null`, defaults to
   `"#ff6666"`.

   @example
      <HTML><HEAD>
         <TITLE>Upload image</TITLE>
         <script src="js/disable_submit_button.js"></script>
         <script src="js/check_file_extension.js"></script>
      </HEAD><BODY>

         <h1>Upload an image</h1>

         <P>Must have an extension of <CODE>.gif</CODE>, <CODE>.jpg</CODE>, or <CODE>.png</CODE>.</P>

         <form id="user_form" method="post" action="/accounts/register/"
                enctype="multipart/form-data">

            <!-- Needed by check_file_extension.js -->
            <span id="message_span_id" class="message_span_id"></span>

            <p><label for="id_profile_picture">Profile picture:</label> <input id="id_profile_picture" name="profile_picture" type="file" /></p>

            <P><I>(To limit the extensions displayed in the open dialog, add</P>

            <BLOCKQUOTE><PRE>accept=".gif,.jpg,.png"</PRE></BLOCKQUOTE>

            <P>to the element)</I></P>

            <!-- Provide a button to click to submit the form. -->
            <input id="submit_button_id" type="submit" name="submit" value="Upload your picture" />
         </form>

      <script language="JavaScript">
         var idFile = "id_profile_picture";  //id of file-upload element
         var idSubmit = "submit_button_id"; //id of the submit button
         function triggerCheckExtension()  {
            //See documentation for additional optional parameters
            checkFileExtension("profile picture", idFile, ["gif", "jpg", "png"], idSubmit);
         }
         document.getElementById("id_profile_picture").addEventListener('change', triggerCheckExtension);
      </script>
      </BODY></HTML>
 **/
function checkFileExtension(file_description, upload_elementId, allowed_extensionArrayNoDot, submit_buttonIdNullIfDontDisable, message_spanID, bad_color) {

   if(message_spanID == null)  {
      message_spanID = "message_span_id";
   }

   var fileElement = null;
   var message = null;  //Message Object span element
   try  {
      fileElement = document.getElementById(upload_elementId);
      message = document.getElementById(message_spanID);
   }  catch(err)  {
      throw  "Attempting to obtain elements by id: upload_elementId (\"" +
      upload_elementId + "\"), message_spanID (\"" + message_spanID + "\"): " + err;
   }

   //Assume valid.
   disableSubmitButtonIfIdNonNull(false, submit_buttonIdNullIfDontDisable);
   fileElement.style.color = null;
   message.style.color = null;
   message.innerHTML = "";

   if(fileElement.value.length == 0)  {
      return;
   }

   var fileExtension = "";
   if (fileElement.value.lastIndexOf(".") > 0) {
      fileExtension = fileElement.value.substring(
         fileElement.value.lastIndexOf(".") + 1, fileElement.value.length);
   }

   var len = allowed_extensionArrayNoDot.length;
   for (var i = 0; i < len; i++) {
      if (fileExtension == allowed_extensionArrayNoDot[i])  {
         return;
      }
   }

   var extensions = "";
   var len = allowed_extensionArrayNoDot.length;
   var lenMinus1 = len - 1
   for (var i = 0; i < len; i++) {
      extensions += allowed_extensionArrayNoDot[i];
      if(i < lenMinus1)  {
         extensions += ", ";
      }
   }

   if(bad_color == null)  {
      bad_color = "#ff6666";
   }

   fileElement.style.color = bad_color;
   message.style.color = bad_color;
   message.innerHTML = "Invalid file-extension for " + file_description +
      ". Only allowed extensions are " + extensions;
   disableSubmitButtonIfIdNonNull(true, submit_buttonIdNullIfDontDisable);
}

passwords_match_alert.js

/**
   @function

   For use on HTML forms containing two password fields, where both must
   match. Provides a green alert when equal, and a red alert when not.
   If either password contains no characters, no alert is given.
   Optionally disables the submit button unless passwords match.

   This function requries `disable_submit_button.disableSubmitButtonIfIdNonNull`

   Derived from (downloaded 8/27/2014)
      http://keithscode.com/tutorials/javascript/3-a-simple-javascript-password-validator.html

   Jeff Epstein: Added the following:

      - all optional parameters
      - The "don't print any alert if one or both fields are empty" feature.
      - The optional "disable submit button feature"
      - Throwing more helpful messages when form elements do not exist.
      - This JSDoc

   With thanks to zkanda on #django irc, for the idea of moving event
   listeners out of form elements.

   @param  {string}  password1_id  "id" identifier (ID) of the
   first-password element.
   @param  {string}  password2_id  ID of the second-password element
   (the "confirm").
   @param  {string}  submit_buttonIdNullIfDontDisable  If the submit
   button should be disabled unless the passwords match. See
   `disable_submit_button.disableSubmitButtonIfIdNonNull`.
   @param  {string}  message_spanId  ID of the span in which to display
   the "do not match!" message. If null, defaults to "message_span_id".
   @param  {string}  good_color  The hex color, including the initial
   hash ('#') to change both the password confirmation, and message-text
   to, when the passwords match. If `null`, defaults to `"#66cc66"`.
   @param  {string}  bad_color  The hex color when the passwords do not
   match. If `null`, defaults to `"#ff6666"`.

   @example
      <HTML><HEAD>
         <TITLE>Create account</TITLE>
         <script src="js/disable_submit_button.js"></script>
         <script src="js/passwords_match_alert.js"></script>
      </HEAD><BODY>
         <h1>Create account</h1>

         <form id="user_form" method="post" action="/accounts/register/">

            <p>Username: <input id="id_username" maxlength="30" name="username" type="text" /></p>
            <p><label for="id_password1">Password:</label> <input id="id_password1" name="password1" type="password" /></p>
            <p><label for="id_password2">Password confirmation:</label> <input id="id_password2" name="password2" type="password" /></p>

            <!-- Where passwordsMatchAlert() writes its message -->
            <span id="message_span_id" class="message_span_id"></span>

            <P><input type="submit" id="id_submit_button" name="submit" value="Create account" /></P>
         </form>

      <script language="JavaScript">
         var idPass1 = "id_password1";  //id of password field 1
         var idPass2 = "id_password2";  //id of password field 2
         var idSubmit = "id_submit_button";  //id of the submit button
         function triggerCheckPass()  {
            //See documentation for additional optional parameters
            passwordsMatchAlert(idPass1, idPass2, idSubmit);
         }
         //"keyup" must be lowercase!
         document.getElementById(idPass1).addEventListener('keyup', triggerCheckPass);
         document.getElementById(idPass2).addEventListener('keyup', triggerCheckPass);
         document.getElementById("id_username").focus();
         document.getElementById(idSubmit).disabled = true;
      </script>
      </BODY></HTML>
 */
function passwordsMatchAlert(password1_id, password2_id, submit_buttonIdNullIfDontDisable, message_spanId, good_color, bad_color)  {
   //Disable submit button by default.
   disableSubmitButtonIfIdNonNull(true, submit_buttonIdNullIfDontDisable);

   if(message_spanId == null)  {
      message_spanId = "message_span_id";
   }


   var pass1 = null;    //Password field elements
   var pass2 = null;
   var message = null;  //Message Object span element
   try  {
      pass1 = document.getElementById(password1_id);
      pass2 = document.getElementById(password2_id);
      message = document.getElementById(message_spanId);
   }  catch(err)  {
      throw  "Attempting to obtain elements by id: password1_id (\"" + password1_id + "\"), password2_id (\"" + password2_id + "\"), message_spanId (\"" + message_spanId + "\"): " + err
   }

   //Compare the values in the password field
   //and the confirmation field
   if(pass1.value.length === 0  ||  pass2.value.length === 0)  {
      //One or both passwords are empty
      pass2.style.backgroundColor = null;//"#FFFFFF";
      message.style.color = null;//"#FFFFFF";
      message.innerHTML = "";
      return;
   }

   if(pass1.value === pass2.value){
      //Set the colors we will be using ...
      if(good_color == null)  {
         good_color = "#66cc66";
      }

      //The passwords match.
      //Set the color to the good color and inform
      //the user that they have entered the correct password
      pass2.style.backgroundColor = good_color;
      message.style.color = good_color;
      message.innerHTML = "Passwords Match!"
      disableSubmitButtonIfIdNonNull(false, submit_buttonIdNullIfDontDisable);

   }  else  {
      if(bad_color == null)  {
         bad_color = "#ff6666";
      }

      //The passwords do not match.
      //Set the color to the bad color and
      //notify the user.
      pass2.style.backgroundColor = bad_color;
      message.style.color = bad_color;
      message.innerHTML = "Passwords Do Not Match!"
   }
}

disable_submit_button.js

/**
   @function
   Disable or enable the submit button if the provided id is non-null.

   @param  {boolean}  is_disabled  If `true`, the button is disabled. If
   `false`, enabled.
   @param  {string}  id_nullIfDontDisable  "id" identifier of the submit
   button, that should be disabled (or enabled). If `null`, this
   function does nothing.
 **/
function disableSubmitButtonIfIdNonNull(is_disabled, id_nullIfDontDisable)  {
   if(id_nullIfDontDisable == null)  {
      return;
   }
   try  {
      document.getElementById(id_nullIfDontDisable).disabled = is_disabled;
   }  catch(err)  {
      throw  "Attempting to get disable element id_nullIfDontDisable (\"" +
      id_nullIfDontDisable + "\"): " + err;
   }
}
Advertisements

Password/password confirm JavaScript form checker, triggered via getElementById().addEventListener()

A javascript function for validating two passwords, which optionally disables the submit button unless the passwords are equal. Source code, including an example-use html page, is below.

empty password
passwords do not match
passwords match

/**
   @private

   Nothing to see here. Move along. Private function needed by the main
   function, just below.
 **/
function _disableSubmitButtonIfIdNonNull(is_disabled, id_nullIfDontDisable)  {
   if(id_nullIfDontDisable == null)  {
      return;
   }
   try  {
      document.getElementById(id_nullIfDontDisable).disabled = is_disabled;
   }  catch(err)  {
      throw  "Attempting to get disable element id_nullIfDontDisable (\"" + id_nullIfDontDisable + "\"): " + err;
   }
}
/**
   @function

   For use on HTML forms containing two password fields, where both must
   match. Provides a green alert when equal, and a red alert when not.
   If either password contains no characters, no alert is given.
   Optionally disables the submit button unless passwords match.

   Derived from (downloaded 8/27/2014)
      http://keithscode.com/tutorials/javascript/3-a-simple-javascript-password-validator.html

   Jeff Epstein: Added the following:

      - all optional parameters
      - The "don't print any alert if one or both fields are empty" feature.
      - The optional "disable submit button feature"
      - Throwing more helpful messages when form elements do not exist.
      - This JSDoc

   With thanks to zkanda on #django irc, for the idea of moving event listeners out of form elements.

   @param  {string}  password1_id  "id" identifier (ID) of the
   first-password element.
   @param  {string}  password2_id  ID of the second-password element
   (the "confirm").
   @param  {string}  submit_buttonIdNullIfDontDisable  ID of the submit
   button that should be disabled, unless the passwords match. If
   `null`, the button is never disabled (or abled).
   @param  {string}  passwordsMatch_messageSpanId  ID of the span in
   which to display the "do not match!" message. If null, defaults to
   "passwords_match_message_span".
   @param  {string}  good_color  The hex color, including the initial
   hash ('#') to change both the password confirmation, and message-text
   to, when the passwords match. If `null`, defaults to `"#66cc66"`.
   @param  {string}  bad_color  The hex color when the passwords do not
   match. If `null`, defaults to `"#ff6666"`.

   @example
      <HTML><HEAD>
         <TITLE>Create account</TITLE>
         <script src="js/passwords_match_alert.js"></script>
      </HEAD><BODY>
         <h1>Create account</h1>

         <form id="user_form" method="post" action="/accounts/register/">

            <p>Username: <input id="id_username" maxlength="30" name="username" type="text" /></p>
            <p><label for="id_password1">Password:</label> <input id="id_password1" name="password1" type="password" /></p>
            <p><label for="id_password2">Password confirmation:</label> <input id="id_password2" name="password2" type="password" /></p>

            <!-- Where passwordsMatchAlert() writes its message -->
            <span id="passwords_match_message_span" class="passwords_match_message_span"></span>

            <P><input type="submit" id="id_submit_button" name="submit" value="Create account" /></P>
         </form>

      <script language="JavaScript">
         var idPass1 = "id_password1";  //id of password field 1
         var idPass2 = "id_password2";  //id of password field 2
         var idSubmit = "id_submit_button";  //id of the submit button
         function triggerCheckPass()  {
            //See documentation for additional optional parameters
            passwordsMatchAlert(idPass1, idPass2, idSubmit);
         }
         //"keyup" must be lowercase!
         document.getElementById(idPass1).addEventListener('keyup', triggerCheckPass);
         document.getElementById(idPass2).addEventListener('keyup', triggerCheckPass);
         document.getElementById("id_username").focus();
         document.getElementById(idSubmit).disabled = true;
      </script>
      </BODY></HTML>
 */
function passwordsMatchAlert(password1_id, password2_id, submit_buttonIdNullIfDontDisable, passwordsMatch_messageSpanId, good_color, bad_color)  {
   //Disable submit button by default.
   _disableSubmitButtonIfIdNonNull(true, submit_buttonIdNullIfDontDisable);

   if(passwordsMatch_messageSpanId == null)  {
      passwordsMatch_messageSpanId = "passwords_match_message_span";
   }


   var pass1 = null;    //Password field elements
   var pass2 = null;
   var message = null;  //Message Object span element
   try  {
      pass1 = document.getElementById(password1_id);
      pass2 = document.getElementById(password2_id);
      message = document.getElementById(passwordsMatch_messageSpanId);
   }  catch(err)  {
      throw  "Attempting to obtain elements by id: password1_id (\"" + password1_id + "\"), password2_id (\"" + password2_id + "\"), passwordsMatch_messageSpanId (\"" + passwordsMatch_messageSpanId + "\"): " + err
   }

   //Set the colors we will be using ...
   if(good_color == null)  {
      good_color = "#66cc66";
   }
   if(bad_color == null)  {
      bad_color = "#ff6666";
   }

   //Compare the values in the password field
   //and the confirmation field
   if(pass1.value.length === 0  ||  pass2.value.length === 0)  {
      //One or both passwords are empty
      pass2.style.backgroundColor = "#FFFFFF";
      message.style.color = "#FFFFFF";
      message.innerHTML = "";
      return;
   }

   if(pass1.value === pass2.value){
      //The passwords match.
      //Set the color to the good color and inform
      //the user that they have entered the correct password
      pass2.style.backgroundColor = good_color;
      message.style.color = good_color;
      message.innerHTML = "Passwords Match!"
      _disableSubmitButtonIfIdNonNull(false, submit_buttonIdNullIfDontDisable);

   }  else  {
      //The passwords do not match.
      //Set the color to the bad color and
      //notify the user.
      pass2.style.backgroundColor = bad_color;
      message.style.color = bad_color;
      message.innerHTML = "Passwords Do Not Match!"
   }
}