Re-Factoring Tip - Clean Up Your Old jQuery Code
We’ve all written quick and dirty code using jQuery to validate a form, handle click events, and do other trivial tasks on a web page. This kind of code has a tendency to accumulate over time, creating technical debt and generally just becoming unwieldy to maintain. JavaScript modules offer a way to clean up legacy jQuery code. With a little bit of effort, and a little bit of imagination, you can turn that old code into a clean JavaScript module.
Take a look at this page, with HTML and JavaScript inline. This kind of code was the norm just a few years ago, and I still see it regularly in my consulting work.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Form</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.error {
color: red;
font-size: 0.9em;
}
</style>
<script>
$(document).ready(function() {
// Validate and submit form
$('#contactForm').submit(function(e) {
e.preventDefault(); // Prevent the default form submission
let isValid = true;
$('.error').remove(); // Remove any previous error messages
// ... [keep your existing name and message validations]
if ($('#email').val().trim() === "") {
$('#email').after('<span class="error">Please enter your email.</span>');
isValid = false;
} else {
// Call server to validate email
$.ajax({
type: "POST",
url: "/validate-email", // This is your server endpoint
data: JSON.stringify({ email: $('#email').val() }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(response) {
if (!response.valid) {
$('#email').after('<span class="error">This email is invalid or not recognized.</span>');
isValid = false;
}
},
error: function(xhr, status, error) {
console.error("Error in AJAX call: ", status, error);
// Optionally, inform user about the error
$('#email').after('<span class="error">Error checking email, please try again.</span>');
isValid = false;
}
});
}
// Check if all validations passed, including AJAX validation
if (isValid) {
// Here you would typically use AJAX to submit the form to your server
// For demonstration, we'll just show a message
$('#formResult').html('<p>Form submitted successfully!</p>');
// Reset form
this.reset();
} else {
// Don't submit the form if there are validation errors
return false;
}
});
// Inline validation on email change
$('#email').on('blur', function() {
if ($(this).val().trim() !== "") {
$.ajax({
type: "POST",
url: "/validate-email",
data: JSON.stringify({ email: $(this).val() }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(response) {
if (!response.valid) {
$('#email').after('<span class="error">This email is invalid or not recognized.</span>');
} else {
$('.error').remove(); // Remove any error message if valid
}
},
error: function(xhr, status, error) {
console.error("Error in AJAX call: ", status, error);
// Inform user about the error
$('#email').after('<span class="error">Error checking email, please try again.</span>');
}
});
}
});
});
</script>
</head>
<body>
<form id="contactForm">
<h2>Contact Us</h2>
<div>
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="message">Message:</label>
<textarea id="message" name="message" required></textarea>
</div>
<button type="submit">Send</button>
</form>
<div id="formResult"></div>
</body>
</html>
Somehow, we have included two calls to the email validation function, so there is duplicate code we need to eliminate. Let’s turn it into a module and include it in the page. We’ll get deferred loading, so we can drop the $(document).ready() call, we will clean up the global space, and we will organize our code to re-use the email validation function.
Let’s make a module. Let’s use a self-executing function to export the module functionality:
export const formValidator = (function(){
})();
Easy! Now we can just drop our form validator into this function and create a useful module.
export const formValidator = (function(){
var isValid = true;
$('#contactForm').submit(function(e) {
e.preventDefault(); // Prevent the default form submission
let isValid = true;
$('.error').remove(); // Remove any previous error messages
// ... [keep your existing name and message validations]
if ($('#email').val().trim() === "") {
$('#email').after('<span class="error">Please enter your email.</span>');
isValid = false;
} else {
// Call server to validate email
$.ajax({
type: "POST",
url: "/validate-email", // This is your server endpoint
data: JSON.stringify({ email: $('#email').val() }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(response) {
if (!response.valid) {
$('#email').after('<span class="error">This email is invalid or not recognized.</span>');
isValid = false;
}
},
error: function(xhr, status, error) {
console.error("Error in AJAX call: ", status, error);
// Optionally, inform user about the error
$('#email').after('<span class="error">Error checking email, please try again.</span>');
isValid = false;
}
});
}
// Check if all validations passed, including AJAX validation
if (isValid) {
// Here you would typically use AJAX to submit the form to your server
// For demonstration, we'll just show a message
$('#formResult').html('<p>Form submitted successfully!</p>');
// Reset form
this.reset();
} else {
// Don't submit the form if there are validation errors
return false;
}
});
})();
But that email validation function is bothersome. Let’s re-factor it into a named function call so we don’t have to have two copies of it.
function validateEmail( selector ){
$.ajax({
type: "POST",
url: "/validate-email", // This is your server endpoint
data: JSON.stringify({ email: selector.val() }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(response) {
if (!response.valid) {
isValid = false;
}else{
isValid = true;
}
},
error: function(xhr, status, error) {
console.error("Error in AJAX call: ", status, error);
isValid = false;
}
});
}
So our re-factored code is cleaner and re-uses the validation function. Note that I have expressly moved the isValid variable up to the top of the script and made it a var. This isn’t the only way to handle this variable, but given how the code is put together, it gets the job done with minimal extra effort.
var isValid = true;
function validateEmail( selector ){
$.ajax({
type: "POST",
url: "/validate-email", // This is your server endpoint
data: JSON.stringify({ email: selector.val() }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(response) {
if (!response.valid) {
isValid = false;
}else{
isValid = true;
}
},
error: function(xhr, status, error) {
console.error("Error in AJAX call: ", status, error);
isValid = false;
}
});
}
export const formValidator = (function(){
$('#contactForm').submit(function(e) {
e.preventDefault(); // Prevent the default form submission
$('.error').remove(); // Remove any previous error messages
// ... [keep your existing name and message validations]
if ($('#email').val().trim() === "") {
$('#email').after('<span class="error">Please enter your email.</span>');
isValid = false;
} else {
// Call server to validate email
isValid = validateEmail( $("#email") );
}
// Check if all validations passed, including AJAX validation
if (isValid) {
// Here you would typically use AJAX to submit the form to your server
// For demonstration, we'll just show a message
$('#formResult').html('<p>Form submitted successfully!</p>');
// Reset form
this.reset();
} else {
// Don't submit the form if there are validation errors
return false;
}
});
$('#email').on('blur', function() {
if ($(this).val().trim() !== "") {
isValid = validateEmail( $("#email") );
}
});
})();
Lastly, we call the module in our web page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Form</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.error {
color: red;
font-size: 0.9em;
}
</style>
<script type="module">
import {formValidator} from './script.js';
</script>
</head>
<body>
<form id="contactForm">
<h2>Contact Us</h2>
<div>
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="message">Message:</label>
<textarea id="message" name="message" required></textarea>
</div>
<button type="submit">Send</button>
</form>
<div id="formResult"></div>
</body>
</html>
Do you see code like this often? There are cases where your inline JS code can get out of hand. Once you start writing a bunch of named functions and have them sitting inline in your HTML page, you find yourself having trouble keeping track of what functionality is where, how it is organized, and what exactly is running. Before it goes too far, get control of it and package it up into chunks you can separate and deal with effectively.
Need some assistance? Drop me a line if you have questions or need someone to clean up your old jQuery code for you.