This tutorial has been converted to a jQuery plugin which you can find here – http://jasonlau.biz/home/a-better-form-a-jquery-plugin

Are you trying to figure out how to reduce or eliminate spam on your website email or comment forms?

My website was slammed by spammers on a daily basis. Typically, the spam attacks would be from robots that would root-out the form fields on each webpage and then automate form submissions which were loaded with spam.

I created an easy solution that does not involve the use of annoying CAPTCHA security phrase generators or similar technology blunders.

My idea does however require javascript and jQuery.

The idea is simple – spam-bots cannot submit a form that does not exist. Nor can spam-bots use a form field which has no name. So, if you remove the form elements from the webpage you effectively stop the robots.

How can I use a form that does not exist? That is easily done with jQuery. I’ll show you how.
  1. Include jQuery in the HTML document’s <head> section before the </head> tag .
    <script src=”http://code.jquery.com/jquery-latest.js” type=”text/javascript”></script>

  2. Add a <noscript> tag to your document body so visitors will know javascript is needed to use the website. I found it’s best to add this tag to the end of the HTML body so that it does not affect how your website is displayed in search engine results. Adding it to the beginning may cause it to show up as the description for your webpage in the search engine results.
    <noscript>Javascript is required to view this webpage properly. Please enable javascript in your browser settings.</noscript>

  3. Create a div container for your form in your HTML document’s <body> section before the </body> tag.
    <div id=”form-container”></div>

  4. Make a form inside the form-container div following these simple rules.
    1. When you make your form do not include the <form> tag. The form tag is a target for robots.
    2. Rather than using the name attribute for each form field, use the id attribute instead. The name attribute is another target for robots.
    3. If you need to include a submit button in your form, use type=button instead of type=submit.
    4. Add disabled=”disabled” to each form element except the first.
    <strong>Email:</strong> <input id=”email” />
    <strong>Comment:</strong>
    <textarea id=”comment” disabled=”disabled”></textarea>
    <input type=”button” id=”sub-button” value=”Submit” disabled=”disabled” />

  5. After the form is assembled, go back to the <head> section where the script for the form will be placed.
  6. Make a jQuery javascript block which will call jQuery on page load. The form script will be placed where noted in the script below.
    <script type=”text/javascript”>

    $(document).ready(function () {

    // Form script goes here

    });
    </script>

  7. I am going to include some basic email address validation in this example which will verify that the email field is actually an email address.
    var checkRegexp = function(o,regexp){
    if(!(regexp.test(o.val()))){
    return false;
    } else {
    return true;
    }
    };

  8. I disabled most of the form fields in step 4-4 so the user must follow a certain order while filling in form data. To make this work, I will make a script to enable or disable each field based on user interaction. Basically, if there is no data in field 1, field 2 is disabled. Field1 is not enabled until field 2 value has changed and contains data, and so on. Also, this is where I implement the email address validation. In this example, when the email field is changed, the address is validated and the user is alerted if validation fails.
    $(“#email”).change(function(){
    if(!$(“#email-tip”).html()){
    $(this).after(‘<div id=”email-tip” style=”display: none; background-color: #FFFFFF;”>Please enter a valid email address.</div>’);
    }

    if($(this).val() != ” && $(this).val() != ‘ ‘ && !checkRegexp($(this),/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i)) {
    $(“#comment”).attr(‘disabled’,'disabled’);
    $(“#email-tip”).show();
    $(“#email-tip”).animate( { fontSize:”2em” } , 500 )
    .animate( { fontSize:”1em” } , 500 );
    } else {
    if($(“#email-tip”).attr(‘display’) != ‘none’){
    $(“#email-tip”).hide(‘slow’);
    }
    $(“#comment”).attr(‘disabled’,”);
    }
    });

    $(“#comment”).change(function(){
    if($(this).val() != ” && $(this).val() != ‘ ‘) {
    $(“#sub-button”).attr(‘disabled’,”);
    } else {
    $(“#sub-button”).attr(‘disabled’,'disabled’);
    }
    });


  9. If you want to eliminate certain types of user input, such as html links or bbcode links, here is a handy little addition I made just for that.
    $(“#comment”).bind(“keyup”,function(){
    var comment_filters = ['url=','link=','http:','www.','href','<a'];
    var comment = $(“#comment”).val();
    for(var i in comment_filters){
    var checkit = comment.split(comment_filters[i]);
    if(checkit.length > 1){
    $(“#comment”).val(checkit[0]);
    }
    }
    });
    That script will basically erase the unwanted input AS the user attempts to type it into the form. The offending input will simply disappear before their very eyes. I have added a number of filters to the variable comment_filters which will eliminate links from the user input. There is no limit to the number of filters I can use. Of course, the user could always type in spaces between each letter to post links, but they will not be click-able, followable, or even copy/paste-able. Too much work for a spammer!

  10. Everything is ready now for the form submission script. Once the user has appropriately filled in all of the form fields the submit button has been enabled and the form is ready to submit.
    In earlier steps I left out the form tag, left out all of the name attributes, and made a regular button instead of a submit button. Now I have to fix all of that, but only after the user has actually interacted with the button. I will bind a click function to the button object so that the user must use a mouse to manually click the button; an action a spam-bot is unable to reproduce. This function will wrap the form-container div with a form tag, will add the name attributes to the input fields, and finally, will submit the form for processing.

    For each input, textarea, or select menu, this script will create a name attribute by copying the id attribute for that object. This only takes place in the form-container so it does not affect other forms on the same page.
    $(“#sub-button”).bind(“click”,function(){
    $(‘#form-container input, #form-container textarea, #form-container select’).each(function(){
    $(this).attr(‘name’,$(this).attr(‘id’));
    });

    $(‘#form-container’).wrap(‘<form id=”comment-form” action=”#” method=”POST”></form>’);
    alert(‘Your comment has been submitted!’);
    $(“#comment-form”).submit();
    });
  11. Save.
So, as you can see, I have created a form that is not really a form at all. My form is lacking the elements that spam-bots rely upon for automated submissions. My method will eliminate most if not all of the automated form submissions on your website.

Here is the complete, working code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<meta name="author" content="Jason Lau" />

<title>JasonLau.biz Robot-less And Spam-less Forms With jQuery</title>
<script src="http://jasonlau.biz/javascript/jquery/latest/jquery-latest.js" type="text/javascript"></script>
<script type="text/javascript">
<!–
/* Spam-less Robot-less Forms Example – (c)2010 JasonLau.biz */
$(document).ready(function(){
var checkRegexp = function(o,regexp){
if(!(regexp.test(o.val()))){
return false;
} else {
return true;
}
};

$("#email").change(function(){
if(!$("#email-tip").html()){
$(this).after('<div id="email-tip" style="display: none; background-color: #FFFFFF;">Please enter a valid email address.</div>');
}
if($(this).val() != '' && $(this).val() != ' ' && !checkRegexp($(this),/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i)) {
$("#comment").attr('disabled','disabled');
$("#email-tip").show();
$("#email-tip").animate({ fontSize:"2em" },500)
.animate({ fontSize:"1em" },500);
} else {
if($("#email-tip").attr('display') != 'none'){
$("#email-tip").hide('slow');
}
$("#comment").attr('disabled','');
}
});

$("#comment").change(function(){
if($(this).val() != '' && $(this).val() != ' '){
$("#sub-button").attr('disabled','');
} else {
$("#sub-button").attr('disabled','disabled');
}
});

$("#comment").bind("keyup",function(){

var comment_filters = ['url=','link=','http:','www.','href','<a'];

var comment = $("#comment").val();
for(var i in comment_filters){
var checkit = comment.split(comment_filters[i]);
if(checkit.length > 1){
$("#comment").val(checkit[0]);
}
}
});

$("#sub-button").bind("click",function(){
$('#form-container input, #form-container textarea, #form-container select').each(function(){
$(this).attr('name',$(this).attr('id'));
});
$('#form-container').wrap('<form id="comment-form" action="#" method="POST"></form>');
alert('Your comment has been submitted!');
$("#comment-form").submit();

});
});
–>
</script>
</head>

<body>

<div id="form-container">
<strong>Email:</strong> <input id="email" /><br />
<strong>Comment:</strong><br />
<textarea id="comment" disabled="disabled"></textarea><br />
<input type="button" id="sub-button" value="Submit" disabled="disabled" />
</div>

<noscript>Javascript is required to view this webpage properly. Please enable javascript in your browser settings.</noscript>

</body>
</html>

Filed under jQuery.

jlEmbed is a plugin for jQuery, which makes it easier to add embedded media players to your webpage. With support for Adobe Flash, Quicktime, Real Player, Silverlight, Windows Media Player, and YouTube, you will no longer need to hard-code lengthy, cumbersome, and invalid HTML for your music or videos. jlEmbed also has built-in music playlist support, a customizable MP3, SWF, FLV, YouTube audio player, support for swfobject, YouTube videos, and custom YouTube video playlists.

jlEmbed’s YouTube API functions allow you to use the YouTube Javascript API and Chromeless YouTube player, giving you the power to create custom YouTube player controls and more!

With jlEmbed, you’ll never receive HTML validation errors again from your Flash or other embedded media! jlEmbed helps keep your webpage validating properly even while using embedded media players!

Valid XHTML 1.0 Transitional

jlEmbed also includes basic plugin detection which kindly links visitors to the appropriate plugin download page when a required plugin is not detected, or automatically loads the user’s default media player plugin.

Embedding music or video on your webpage is a snap with jlEmbed!
$("#example-id").jlEmbed({ url: 'http://jasonlau.biz/public/mixdown.mp3' });
That short, simple code installs the default Media Player with default options on the object example-id!

Tested on the latest versions of the following browsers:
Internet Explorer
Internet Explorer
Firefox
Firefox
Chrome
Chrome
Opera
Opera
Safari
Safari
Please report any bugs or errors by using the Contact Form. jQuery is required for jlEmbed to work. Be sure to include jQuery before attempting to include jlEmbed.
Changelog:
  • 4.9.7.2 – Removed some line breaks in the output code. Youtube video automatically cues when autoplay option is false. Shuffle now works with the playlist option to randomize the m3u when generated. Youtube and adobeflash default to swfobject format.
  • 4.9.7.1 – Fixed a typo. Saved as UTF-8 encoding.
  • 4.9.7 – Added m3u_url option (see options tab for more details).
  • 4.9.6 – Fixed plugin detection bug which crashed jlembed in IE7. Added value option (see options tab for details). Changed the way youtubedebug works. Added a few more hidden fields for the YouTube player. You can now see all of the hidden fields for the YouTube player by setting youtubedebug to true.
  • 4.9.5 – Fixed bug which caused YouTube player to not initiate playback when autostart option was set to true.
  • 4.9.4 – Fixed bug which caused Real Player to not work in Firefox while using object only format.
  • 4.9.3 – Fixed bug which caused Windows Media Player to not work in Firefox while using object only format.
  • 4.9.2 – The last update didn’t fix everything as I had thought. Had to go back and rearrange some code.
  • 4.9.1 – Fixed autoplay and loop bug which caused autoplay and loop to not work. Updated documentation to reflect some of the recent updates. For example, all playlist links must be separated by a |pipe symbol.
  • 4.9 – Fixed YouTube autoplay.
  • 4.8 – Fixed swfobject params bug which caused an error when using params to set player parameters.
    YouTube wmode is now set to opaque by default to correct potential z-index issues.
    Now, the z-index for the YouTube player object can be set to allow other objects with a higher z-index to float on top of the video window.
    YouTube allowscriptaccess is set to always by default as this is required for javascript api interaction.
  • 4.7 – Fixed YouTube player default volume and set to value of 100.
  • 4.6 – Fixed private option youtubeapiid bug which caused SWFObject to reverse the order of multiple embedded videos because all instances of jlEmbed used the same SWFObject div id.
    Changed private option youtubeapiid to swfobjectuniqueid as it is a more accurately descriptive name.
  • 4.5 – Added YouTube hidden field jlembed_yt_player_state_{playerId} for ease of event listening.
    Added YouTube hidden field jlembed_yt_loop_{playerId}
    Added YouTube hidden field jlembed_yt_playlist_{playerId}
    Added YouTube hidden field jlembed_yt_current_playlist_item_{playerId}
    Removed depreciated YouTube hidden field jlembed_yt_volume_{playerId}
    Changed span id’s for YouTube debug output to include jlembed_debug_ prefix.
    All YouTube functions which did not previously return data now return a boolean value.
    To avoid confusion and errors, all playlist and titles array items should now be separated by a |pipe symbol where there was previously a space or comma.
    To avoid confusion and errors, all options which had string values yes and no are now boolean values true and false except the musicplayer options. youtube option now accepts a playlist consisting of |pipe separated YouTube video Id’s.
    Added the option shuffle for YouTube playlist.
    loop option is now accessible to the YouTube player.
  • 4.4 – Fixed SWFObject to work with all Adobe Flash when the format is set to swfobject.
  • 4.3 – Changed function onYouTubePlayerReady to cue the chromeless player when autoplay is off.
    Added missing variable startSeconds to function cueVideoByUrl.
    Published jlEmbed YouTube demo page.
  • 4.2 – Removed depreciated reference to jlembedplayer in YouTube functions.
    Removed depreciated function jlembed_clearVideo.
  • 4.1 – Fixed bug in YouTube function jlembed_loadVideoByUrl
  • 4.0 – Major version change.
    Added swfobject support, which can be accessed using the format option. Refer to the documentation for the format option.
    Added support and options for YouTube, a full array of YouTube javascript API functions , and YouTube chromeless player support.
    Added youtube, youtubekey, youtubechromeless, youtubevolume, youtubedebug, youtubedisplay, flash_version options.
  • 3.1 – Fixed plugin detection bug.
    Defined default settings for a few options which were previously undefined.
    Changed default height to 45px from 48px.
    Fixed url and src option bug.
    Renamed base64 and utf8 encoding functions for compatability.
    Beautified code structure.
    Added YouTube support for the Flash music player.
  • 3.0 – Numerous changes since the earlier versions. Added musicplayer, playlist, and titles options, which incorporate some of my other scripts into jlEmbed. Also, added this changelog to help keep track of what I’ve changed with each version.

Filed under .