I recently completed my first website project using the very impressive Perch CMS - I loved using the CMS and I’ll be writing a review of it shortly. However, one thing I had a little trouble with was submitting a form using ajax - there were no real resources or information to help me out. I also noticed there was quite a bit of interest in the forums about how to do this - so I decided to write a quick guide to let other users know how I did it.
Form add-on quirks / annoying things
There are a few little quirks with the Perch forms add-on which can make submitting forms with ajax a bit confusing.
The first, and by far the most annoying for me, is that the forms add-on changes the id of the form. It prepends ‘form1_’ to your first form, ‘form2_’ to the second etc. So for quite a while I was wondering why none of my jQuery code was working at all, turns out the form id was not what I thought it was - I should have checked sooner, but this was still really annoying. It actually surprised me because one of the key features of Perch is that it doesn't interfere with your markup at all, unlike some other CMS's.
The other thing is the form add-on documentation has nothing about how to use it with ajax, so it’s basically just trial and error based on experience with other CMS’s. I would’ve appreciated a short guide to tell me what URL to post to, if the form add-on provides a response from the server etc. And the information in the forums from the developers is really not helpful on this issue either. Hopefully this guide will help fill this gap in the docs.
Step 1. Create the Perch region and form template
The first step is to create the region to output the form and create the contact form template. I’m using bootstrap, so if you’re planning on copying this code, make sure you have Bootstrap loaded on your page or edit the code accordingly.
Create the region by adding the following snippet to you page:
<?php perch_content('Contact'); ?>
Then create your contact form template, I’ve included my entire contact section template, but you can just use the form part or whatever you need:
<section id="contact" class="content-section"> <div class="container"> <div itemscope itemtype='http://schema.org/LocalBusiness' id="contact-details" class="col-sm-4"> <h2>Details</h2> <hr class="visible-xs-block" /> <dl itemprop='contactPoint' itemscope itemtype='http://schema.org/ContactPoint'> <meta itemprop='contactType' content='customer service'> <dt>Phone</dt> <dd><p itemprop="tel" item><strong span itemprop="type">Office:</strong> <span itemprop='telephone'><perch:content id="office-phone" type="text" label="Office phone" /></span></p></dd> <dd><p itemprop="tel" item><strong span itemprop="type">Mobile:</strong> <span itemprop='telephone'><perch:content id="mobile-phone" type="text" label="Mobile phone" /></span></p></dd <dt>Email</dt> <dd><p><span itemprop='email'><perch:content id="contact-email" type="text" label="Contact email address" /></span></p></dd> </dl> </div> <div id="enquiry-form" class="col-sm-8"> <h2>Enquiry Form</h2> <hr class="visible-xs-block" /> <perch:form id="contact-form" method="post" action="" app="perch_forms" role="form"> <div class="row"> <div class="col-sm-6"> <div class="form-group"> <perch:label for="name">Your name (required)</perch:label> <div class="input-group"> <span class="input-group-addon"><i class="fa fa-user"></i></span> <perch:input type="text" required="true" class="form-control input-lg" id="name" label="Name" placeholder="Your name" /> </div> </div> </div> <div class="col-sm-6"> <div class="form-group"> <perch:label for="organisation">Organisation</perch:label> <div class="input-group"> <span class="input-group-addon"><i class="fa fa-briefcase"></i></span> <perch:input type="text" class="form-control input-lg" label="Organisation" id="organisation" placeholder="Organisation" /> </div> </div> </div> <div class="col-sm-6"> <div class="form-group"> <perch:label for="phone">Phone</perch:label> <div class="input-group"> <span class="input-group-addon"><i class="fa fa-phone"></i></span> <perch:input type="text" class="form-control input-lg" id="phone" label="Phone" placeholder="Phone number" /> </div> </div> </div> <div class="col-sm-6"> <div class="form-group"> <perch:label for="email1">Email address (required)</perch:label> <div class="input-group"> <span class="input-group-addon"><i class="fa fa-envelope"></i></span> <perch:input type="email" class="form-control input-lg" required="true" label="Email address" id="email" placeholder="Email" /> </div> </div> </div> <div class="col-sm-12"> <div class="form-group"> <perch:label for="message">Your message (required)</perch:label> <perch:input type="textarea" id="form-message" name="message" required="true" encode="false" label="Message" class="form-control input-lg" rows="12"></textarea> </div> <perch:input type="submit" id="submit" value="Send" class="btn btn-blue btn-fixed-width" /> </div> </div> </perch:form> </div> <div id="success-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="success-modal-Label" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h3 class="modal-title" id="success-modal-Label"><i class="fa fa-check-circle green"></i> Thanks for getting in touch!</h3> </div> <div class="modal-body"> <p>Your enquiry has been sent successfully, we will be in touch ASAP with a response.</p> </div> </div> </div> </div> <div id="error-modal" class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="error-modal-label" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h3 class="modal-title" id="error-modal-label"><i class="fa fa-times-circle red"></i> Error - Enquiry was not sent</h3> </div> <div class="modal-body"> <p>Something went wrong submitting your enquiry, it's probably just a temporary problem so you should try again in a few minutes.</p> <p>If you find you the form just won't work you could email us directly at 'info@rbcenv.com'</a>. Sorry for any inconvenience this has caused you.</p> </div> </div> </div> </div> </section>
Note the bootstrap modals at the end, these are important, this is where the success and failure messages will appear.
Step 2. The javascript
This is where things get a little more complicated. I don’t want to just submit this form - I want to validate it first. I do this using the outstanding jQuery.validate jQuery plugin. The full code to validate and submit the form appears below, I’ll discuss what every section does in turn. For more info you should consult the jQuery.validate documentation.
// Validate form and submit by ajax $("#form1_contact-form").validate( { rules: { name: { required: true }, email: { required: true, email: true }, phone: { required: false, digits: true }, message: { required: true } }, errorPlacement: function(error, element) { error.appendTo( element.closest("div.form-group")); }, messages: { name: "Please enter your name", phone: { digits: "Phone number can only contain numbers" }, email: { required: "We require your email address to contact you", email: "Your email address must be in the format of name@domain.com" }, message: "Please enter your message" }, highlight: function(element, errorClass) { if($(element).is('input') && !$(element).closest('div.form-group').find('.glyphicon-remove').length) { $(element).closest('div.input-group').after('<span class="glyphicon glyphicon-remove form-control-feedback" aria-hidden="true"></span>'); } $(element).each(function() { $(this).closest('div.form-group').addClass('has-error has-feedback'); }); }, unhighlight: function(element, errorClass) { $(element).closest('div.form-group').find('.glyphicon-remove').remove(); $(element).closest('div.form-group').removeClass('has-error has-feedback'); }, invalidHandler: function(event, validator) { if($('.glyphicon-remove').length) { $('.glyphicon-remove').remove(); } }, submitHandler: function(form) { // Change submit value to 'Processing...' so user knows somwething is happnin $(form).find( "[type=submit]" ).attr('value', 'Processing...'); // Get the post data var data = $(form).serialize(); // Send it to the server $.post('/', data, function() { // Show success modal if form was successfully submitted $('#success-modal').modal('show'); // Reset form back to default values on successful submission $('#form1_contact-form').trigger("reset"); }) .fail( function() { // Show error modal if there was any kind of error $('#error-modal').modal('show'); }) .done(function() { // Reset the submit button to say 'Send' again whether //the form aubmitted successfully or not $(form).find( "[type=submit]" ).attr('value', 'Send'); }); } });
So, from the top.
’rules’ is just an object which defines what fields are required and whether they need to have specific types of info entered, eg. email address etc. Edit this to match your form, info can be found at the jQuery.validate docs.
‘errorPlacement’ is a method which defines where in the markup form errors should be placed, the code here is specific to bootstrap form markup.
‘messages’ is an object which allows you to define the error messages for each validation test.
‘highlight’ is a method which allows you to define what happens when an error is highlighted - the code here is specific to bootstrap markup.
‘unhighlight’ is a method which basically just undoes what we did in ‘highlight’, also specific to bootstrap.
‘invalidHandler’ is a method which defines what to do when an invalid form is submitted. In this case it just removes all the existing errors icons so we don’t get multiple icons appearing if an invalid form is submitted more than once.
‘submitHandler’ is the method that handles submitting the form - this is the method that interacts with the perch forms add-on. I’ll cover this method in more details.
Firstly, I find the submit button and change it’s ‘value’ to ‘Processing…’ just so the user knows something is going on if the form takes a while to submit:
// Change submit value to 'Processing...' so user knows something is happnin $(form).find( "[type=submit]" ).attr('value', 'Processing...');
Next, I store the form content in the variable ‘data’:
// Get the post data var data = $(form).serialize();
Then I send that data to the server using the jQuery .post() method and define the success callback, which shows the success modal if the form is successfully submitted, it also resets the form fields to their default values after submission. Note, I just send it to the website root, ‘/‘ - this works fine, but it’s not documented as the way to do it anywhere:
// Send it to the server $.post('/', data, function() { // Show success modal if form was successfully submitted $('#success-modal').modal('show'); // Reset form back to default values on successful submission $('#form1_contact-form').trigger("reset"); })
Next, I define the ‘fail’ callback, this method will fire if the form is not able to be submitted. It just shows the error modal:
.fail( function() { // Show error modal if there was any kind of error $('#error-modal').modal('show'); })
Finally, I define the ‘always’ callback, this method resets the submit button value to ‘Send’ whether the form was able to be submitted or not:
.always(function() { // Reset the submit button to say 'Send' again whether // the form submitted successfully or not $(form).find( "[type=submit]" ).attr('value', 'Send'); });
Step 3. The CSS
For the validation to look right you’ll need to apply some CSS. The CSS I used is included below, but you should edit it to match your needs. This CSS is specific to the bootstrap framework.
div.form-group.has-error input, div.form-group.has-error textarea, div.form-group.has-error span.input-group-addon { color:#f2385a!important; border: solid 1px #f2385a!important; } div.form-group.has-error .glyphicon-remove { color:#f2385a!important; } div.form-group.has-error span.input-group-addon { background:#f2dede!important; border-right:none!important; } span.input-group-addon { box-shadow: 0 0 0 rgba(102,175,233,.6); border-right:none!important; } #enquiry-form input, #enquiry-form textarea, span.input-group-addon { border: solid 1px #000d27; } #enquiry-form input:focus, #enquiry-form textarea:focus { border: solid 1px #117ec1; box-shadow:none; color:#117ec1; } @media only screen and (max-width:767px) { #enquiry-form { padding-top:2em!important; } section#contact h2:first-child { margin-top:0!important; } } label.error { position:absolute; margin-top:.5em; color: #f2385a; font-weight:normal; } @media all and (max-width:991px) { form .has-feedback label ~ .form-control-feedback { margin-top:8px; } } @media all and (min-width:992px) { form .has-feedback label ~ .form-control-feedback { margin-top:7px; } }
Additional Tip - Form field focus behaviour
I used the following jQuery code to get the form field focus behaviour to work how I wanted, it may be useful to you. On a focus event it: saves the original placeholder text (which is stored in a data attribute in the markup), clears the placeholder text, and applies the correct styles. then, on blur, it basically just reverses it all ('#enquiry-form' is the id of the div which encloses my form, not the form itself).
// Form onfocus behaviour $(function(){ var origCss = $('.input-group-addon').first().css(['border-color', 'background-color', 'color']); $('#enquiry-form input').focus(function() { $(this).attr('data-orig-placeholder', $(this).attr('placeholder')); $(this).attr('placeholder', ''); $(this).siblings('.input-group-addon').css( { 'borderColor' : '#117ec1', 'backgroundColor' : '#cfe5f3', 'color' : '#117ec1' }); }); $('#enquiry-form input').blur(function() { $(this).attr('placeholder', $(this).attr('data-orig-placeholder')); $(this).siblings('.input-group-addon').css(origCss); }); });
And in closing...
I hope this has helped some fellow Perch lovers out there to make better forms. I’ll be doing a review and possibly some more ajax guides in the coming weeks, so if you’re interested in that check back.
If you’ve had any problems getting this code to work or have some comments or suggestions, please leave a comment below.