On occasions it is useful to be able to “intercept” the normal submit behaviour of a form in order to validate it, send it to the server (via ajax) and then display the result to the user without reloading the page in the browser. In this article we outline one approach to achieving this with jquery for forms that have been generated dynamically within Expression Engine 1.6.X templates.

Desired functionality

For this comment form I didn't want to provide any type of preview screen to the user as the idea of the layout of the form is to present the main comment field exactly as it will appear when submitted. Also since comments are moderated (to avoid spam), and as such are not displayed immediately to the user, the code does not concern itself with displaying the content of the response from the server. Indeed the only response that the user sees is if the submission was successful or not (as determined by jQuery and our javascript code).

Required javascript files

In order to use the jQuery and the jQuery validation plugin code you need to define the scripts with something like the following (Note that here we're using a couple of CDNs but you could just as easily link to the files on a local server, if necessary):

<script type="text/javascript" 
  src="http://code.jquery.com/jquery-1.4.2.min.js">
</script>
<script type="text/javascript" 
  src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js">
</script>

Expression Engine 1.6.X template code

This is the code that we use to generate the comment form. For the sake of brevity only the relevant part of the template is included below. The enclosing html, multilingual tags and other superfluous EE tags have been omitted.

Points of interest in the template extract:

  • The Expression Engine tags appear without their enclosing {} (as this appears to cause a problem with displaying the code).
  • The entire extract is contained within a exp:weblog:entries tag, which allows us to access the allow_comments boolean (line 1) for this particular weblog entry.
  • The comment form itself is created with the exp:comment:form tag (line 4) which uses the information contained within the URL to identify the current weblog entry.
  • All label tags contain a for attribute whose value is equal to the name attribute of the corresponding input field which means that when you click on the label the focus goes to the referenced input field.
  • Three hidden p elements are identified by their id attributes and are used by our javascript to display the appropriate message to the user.
  • All the hard coded strings in headers, input field labels and buttons and the success/error messages can easily be changed to dynamic tags to allow the page to be generated in response to the language of the user and/or the content of each weblog entry.
  • The CSS classes, required, email and url, in the class attribute on the input fields are used by the jQuery validation plugin to identify the type of validation to apply.

The template extract:

if allow_comments
<div id="add-comment" class="group">
  <h3>Add a comment</h3>
  exp:comment:form
    <div id="add-comment-fieldsets" class="group">
      <div class="left">
        <fieldset>
          <label for="name">Name:<em>*</em></label>
          <input type="text" name="name" 
            value="" id="name" size="37" class="required"/>
        </fieldset>
        <fieldset>
          <label for="email">Email:<em>*</em></label>
          <input type="text" name="email" 
            value="" id="email" size="37" class="required email"/>
        </fieldset>
        <fieldset>
          <label for="url">URL:</label>
          <input type="text" name="url" 
            value="" id="url" size="50" class="url"/>
        </fieldset>
      </div> <!-- /.left -->
      <div class="right">
        <fieldset>
          <label for="comment">Comment:<em>*</em></label>
          <textarea name="comment" id="comment" 
            rows="10" cols="35" class="required"></textarea>
        </fieldset>
      </div> <!-- /.right -->
    </div> <!-- /#add-comment-fieldsets -->
    <p id="flash-message-ok" 
      style="display:none">Add comment ok</p>
    <p id="flash-message-oops" 
      style="display:none">Add comment oops</p>
    <p id="flash-message-timeout" 
      style="display:none">Add comment timeout</p>
    <input type="submit" name="send" value="Send comment"/>
  /exp:comment:form
</div> <!-- /#add-comment -->
/if

As a general comment on forms, it is a good idea to make sure that the name attribute of the submit button in any form that you create is not submit. This is a good habit to get into because if you use javascript (either your own code or a third party library) to do any client side pre-submit processing on the form then the chances are that at the end of this processing you will want to submit the form.

In order to do this something like form.submit() will need to be called but if the form also contains an element whose name attribute is submit then javascript can become confused between the forms' submit() method and its' element whose name attribute is submit. The end result being that both of them receive a call and the form gets submitted to the server twice. The best way to avoid this to always set the name attribute of the submit button to something other than submit.

JQuery javascript

The fadeInFadeOut function displays the given jQueryElement and then uses the setTimeout method to hide it at the end of the given timeout.

The validation of the comment_form is attached to the #comment_form element as identified by it's id because in any given page there will only ever be one comment_form. Note that the underscore in the id is HTML that is generated by Expression Engine, whereas the rest of the HTML uses a dash to separate words in ids and class names.

This validation uses the class attributes of each input element (as described above) to determine how it's content should be validated.

As part of the call to validate we define a submitHandler function which allows us to receive the form that is being validated as a parameter in order to control it's subsequent submission.

Within this handler we use the jQuery ajax function to submit the form. The ajax function takes as a parameter a Hash of name value pairs (in json format) which are used to configure both the request that is sent and how the response is handled.

The arguments are:

  • The url is extracted from the attribute action of the form under validation.
  • The type that the browser uses to send the request is POST.
  • The data key contains the data that is to be sent in this request.
  • The success key contains a callback function - more on this a little bit later on.
  • The error key also contains a callback and we use this to display our Oops message to the user.

The javascript source code is:

$(function(){
  // Show an element and then fade it out with the timeout given
  var fadeInFadeOut = function(jQueryElement, timeout) {
    jQueryElement.fadeIn(300);
    setTimeout(function() { jQueryElement.fadeOut("slow"); }, timeout);
  }; 

  // Validate the comment / message forms
  $("#comment_form").validate({
    submitHandler: function(form) {
      $.ajax({
        url: $(form).attr("action"),
        type: "POST",
        data: $(form).serialize(),
        success: function(response) {
          if(response
            .match(/you are only allowed to post comments every 90 seconds/i)) {
              fadeInFadeOut($("#flash-message-timeout"), 2500);
          } else if (response.match(/&lt;title&gt;error/i)) {
            fadeInFadeOut($("#flash-message-oops"), 2000);
          } else {
            //Add a success message     
            //Show it and then configure a timeout to delete it
            fadeInFadeOut($("#flash-message-ok"), 2000);
            //Delete the input fields only if the message has been sent
            $(":input[type^='text']", form).attr('value', '');
            //Replace the EE XID with a new one
            $.get($(form).attr("action"), function(responseXID) {
              $("#comment_form :input[name='XID']").val($(responseXID)
                .find("#comment_form :input[name='XID']").val());
            });
          }
        },
        error: function() {
          //Add an oops message
          //Show it and then configure a timeout to delete it
          fadeInFadeOut($("#flash-message-oops"), 2000);
        }
      });
    }
  });
});

In the example above, for the value associated with the data key in the parameters Hash that is passed to the ajax function we use the jQuery serialize function (which is present in the object that jquery uses to represent a set of form elements) to serialize all of the data contained within the form that is currently being validated. This allows us to send not only the values that are added by the user in the textual input fields but also any hidden fields that might be present now or in the future.

It would also be here within the parameter referenced by the data key that we would add something like {"method": "DELETE"} or {"method": "PUT"} (the exact format depends on the framework being used) to indicate the request type when submitting to a restful API.

In the following screen shot you can see the hidden input elements that are added by Expression Engine in the div class="hiddenFields" element as well as the p #flash-message-ok, #flash-message-timeout and #flash-message-oops messages.

In order to understand the logic in the function that we use as the value of the success key, it is necessary to understand a couple of the security options that Expression Engine provides and how these relate to the HTTP status codes that are used to return information when a form is submitted, as well as how jQuery determines if a request has been successful.

When the response from the server contains a HTTP status code of 200 OK or 302 Found jQuery calls the function associated with the success key (this jQuery behaviour can be modified but we don't need to do that here). Since EE returns an error page with an HTTP status of 200 OK when something has prevented EE from processing the comment, the success key function is also called in these cases. This means that in order to present an accurate message to the user we have to have a look at the contents of the response to identify what has happened and we use standard Javascript regular expressions to do this.

The option "Comment resubmission time interval" that EE provides under Admin > Section Administration > Section Management > Edit Section Preferences inside the "Comment posting preferences" subsection allows an administrator to define a time interval which must elapse between the submission of two comments from the same IP address (this is just one of a number of throttling options that EE provides). When this time limit has not elapsed, EE returns a 200 OK HTTP status and an error page which contains the string "you are only allowed to post comments every $VALUE seconds" (where $VALUE is the time interval defined as the "Comment resubmission time interval").

Therefore, the first if condition contains a check for this string and if it is present we display the message contained in the hidden #flash-message-timeout element. The second if condition checks in the title element for any generic error pages and when true it displays the generic error message in the hidden #flash-message-oops element. This second condition is useful if you have activated the "Deny Duplicate Data" option which can be found in the Admin > System Preferences > Security and Session Preferences section.

The Admin > System Preferences > Security and Session Preferences section also contains the option "Process form data in Secure Mode" and when this is active a hidden field called XID is populated in the .hiddenFields div that EE adds to the comment form. The value of this XID field must be unique for each request that is sent to EE and since we want to allow multiple comment submissions without being required to reload the page to obtain a new XID, the jQuery get function is used to retrieve a new XID and then replace the previous value in the comment form (the 3 lines following the comment

//Replace the EE XID with a new one

in the code above).

When a user who is logged in creates a comment Expression Engine returns a 302 Found HTTP status and an updated version of the current page, which contains a form with a new XID. At present this is sufficiently infrequent (since only administrators can log in) that no attempt has been made in the Javascript code to use any of the information contained in this response. However, this would be something to take into account if user accounts were to be added to the site in the future as in that case it would be worthwhile to use all or some of the content of this response in order to save ourselves the subsequent call to the jQuery get function.

Applications of this approach

This approach allows the programmer to enhance an already functioning form by applying some javascript to it in an unobtrusive manner (i.e. the javascript code is not mixed in with the HTML markup). Using javascript in this way with an already functioning form means that we have a form that will both work correctly in browsers that do not have javascript enabled as well as provide an augmented user experience (hopefully!) on javascript enabled devices. By adapting this approach additional functionality can be added to any form. For example, the success: callback function can easily be changed to use the response from the server to add, update or replace visible elements in the DOM.

Filed under: languages, web

comments

by nice

11 October, 2010 at 0:46

nice post