Using non-jQuery AJAX form submission in CMSMS
Previous article Next articleA guestblog from the Dutch forum member Guido, also known as the developer of the ACE Editor 2 module for CMSMS™.
As someone who tries to stay away from jQuery solutions (not because I think jQuery is bad, it is very good) I worked out a way to submit a form AJAX-style in CMS Made Simple™. Let me walk you through the steps.
Note: I will not be demonstrating the steps for creating a form (I recommend using FormBuilder), since this is outside of the scope for this tutorial. I will, however explain which parts of the code are ‘custom’ and should be adapted to your situation.
How to use
STEP 1
What this does:
It takes a wrapping div called ‘product_quote’ (adjust to your wrapping div’s id) and gets the form element inside (generated by your {FormBuilder} tag), and places it in a variable called ‘pQuote’. You can adjust this var name, but remember to rename all other instances as well.
STEP 2
What this does:
It takes the previously created ‘pQuote’ var (your form) and changes the action parameter (what happens when you submit the form). It sets it to the URL of the current page (‘window.location.href’) and adds ‘?showtemplate=false’ to it. This is where CMSMS comes in. The added parameter ‘?showtemplate=false’ tells CMSMS to only give back the {content} tag’s content and not use the page template. This allows you to use FormBuilders ‘Sent Template’ as a standard text to show visitors on form submission.
The code above assumes you use pretty urls at your website.
If not, change ‘?showtemplate=false’ to ‘&showtemplate=false’
STEP 3
var data = serialize(pQuote);
var r = new XMLHttpRequest();
r.onreadystatechange = function() {
if (r.readyState == 4 && r.status == 200) {
showResponse(r.responseText);
}
}
r.open('POST', window.location.href+'?showtemplate=false');
r.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
r.send(data);
e.preventDefault();
}
What this does:
It takes the previously created ‘pQuote’ var (your form) and adds an ‘onsubmit’ handler. Basically: something that happens when you click ‘submit’ (or submit by pressing ENTER). It then executes a function that passes in the ‘e’ variable (the submitting event).
Now look at this line:
The ‘serialize’ function is not a standard JavaScript function, I will provide the code for this function at the end. You should include it on your page via a template, HTML block or separate JS file. What this does is, it takes the form and prepares the data on it to be sent through JavaScript's native AJAX method. jQuery has a similar function built-in.
The code above assumes you use pretty urls at your website.
If not, change ‘?showtemplate=false’ to ‘&showtemplate=false’
STEP 4
Now look at this part of the code:
r.onreadystatechange = function() {
if (r.readyState == 4 && r.status == 200) {
showResponse(r.responseText);
}
}
r.open('POST', window.location.href+'?showtemplate=false');
r.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
r.send(data);
e.preventDefault();
What this does:
This is the native JavaScript way of sending an AJAX request (jQuery wraps its own function around this, but basically it’s the same thing). We create a request and store it in variable called ‘r’. We then use the standard ‘onreadystatechange’ event (a handler that listens to the request and knows when it changes) to know when the request is complete and successfull when readyState is ‘4’ and status is ‘200’.
When a native JavaScript AJAX call is completed, it will send back the response in a variable called ‘responseText’ and attach it to the ‘r’ object (our request object). We use a self-written function called ‘showResponse’ to handle this data.
After that, we open the request (‘r.open’) and tell it we want to use POST (standard for form submission), where we want to post to (again, the same URL as we used to update the ‘action’ parameter of the form).
We set the request header to make sure the data is handled correctly and lastly, we send the data we serialized in step 3.
Lastly, but important: We use the ‘e’ variable we set-up earlier and use the method ‘preventDefault()’ on it. Remember the ‘e’ was the submitting event? We don’t want to use the default action, which is point the browser to the ‘action’ parameter of the form (which would show us the FormBuilder's ‘Send Template’ without further markup from a page template).
STEP 5
var respDiv = document.getElementById('quote_response');
respDiv.innerHTML = r;
addClass(respDiv, 'active');
setTimeout(function(){
removeClass(respDiv, 'active');
},5000);
}
What this does:
This is a separate function that handles the response (the text we set in FormBuilder's ‘Sent Template’ section of our form). It uses a variable called ‘r’ (not to be confused with the ‘r’ variable we used for the request, this is the response. It gets an element called ‘quote_response’). You can get any other element on your page you want, so change this ID to the one you need.
It then sets the response from the FormBuilder module to be the inner HTML (so content) of your response DIV.
TIP: you could also change the ID here to the form-wrapping DIV ID. It will then replace your form with the response-text.
In my case, I wanted to show the response div for 5 seconds. So I used some self written functions that adds an ‘active’ class, and removes it 5 seconds later. I then used CSS to style the 'active' and ‘inactive’ state.
ALL TOGETHER
var pQuote = document.getElementById('product_quote').getElementsByTagName('form')[0];
pQuote.action = window.location.href+'?showtemplate=false';
pQuote.onsubmit = function(e){
var data = serialize(pQuote);
var r = new XMLHttpRequest();
r.onreadystatechange = function(){
if (r.readyState == 4 && r.status == 200) {
showResponse(r.responseText);
}
}
r.open('POST', window.location.href+'?showtemplate=false');
r.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
r.send(data);
e.preventDefault();
}
function showResponse(r) {
var respDiv = document.getElementById('quote_response');
respDiv.innerHTML = r;
addClass(respDiv, 'active');
setTimeout(function(){
removeClass(respDiv, 'active');
},5000);
}
}
sendAjaxForm ();
In the page above, it all comes together, with one addition: it’s all wrapped in a function called ‘sendAjaxForm’. This function is then called after.
Extra functions
The helper functions I wrote (or took from StackOverflow, thank you) you need for this are:
if (element.className != "") {
var classArray = element.className.split(" ");
for (i = 0; i < classArray.length; i++) {
if (classArray[i] == classToCheck) {
return true;
break;
}
}
}
}
function addClass(element, classToAdd) {
if (element != null && element != undefined) {
if (!hasClass(element, classToAdd)) {
if (element.className != "") {
element.className = element.className+" "+classToAdd;
} else {
element.className = classToAdd;
}
}
}
}
function removeClass(element, classToRemove) {
if ((element.className == classToRemove) || (element.className == "")) {
element.className = "";
} else {
var classArray = element.className.split(" ");
var newClass = "";
for (i = 0; i < classArray.length; i++) {
if (classArray[i] != classToRemove) {
newClass += classArray[i]+" ";
}
}
element.className = newClass.trim();
}
}
Serialize
/**
* Adapted from {@link http://www.bulgaria-web-developers.com/projects/javascript/serialize/}
* Changes:
* Ensures proper URL encoding of name as well as value
* Preserves element order
* XHTML and JSLint-friendly
* Disallows disabled form elements and reset buttons as per HTML4 [successful controls]
* {@link http://www.w3.org/TR/html401/interact/forms.html#h-17.13.2}
* (as used in jQuery). Note: This does not serialize <object>
* elements (even those without a declare attribute) or
* <input type="file" />, as per jQuery, though it does serialize
* the <button>'s (which are potential HTML4 successful controls) unlike jQuery
* @license MIT/GPL
*/
function serialize (form) {
'use strict';
var i, j, len, jLen, formElement, q = [];
function urlencode (str) {
// http://kevin.vanzonneveld.net
// Tilde should be allowed unescaped in future versions of PHP (as reflected below),
// but if you want to reflect current PHP behavior,
// you would need to add ".replace(/~/g, '%7E');" to the following.
return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').
replace(/\)/g, '%29').replace(/\*/g, '%2A').replace(/%20/g, '+');
}
function addNameValue(name, value) {
q.push(urlencode(name) + '=' + urlencode(value));
}
if (!form || !form.nodeName || form.nodeName.toLowerCase() !== 'form') {
throw 'You must supply a form element';
}
for (i = 0, len = form.elements.length; i < len; i++) {
formElement = form.elements[i];
if (formElement.name === '' || formElement.disabled) {
continue;
}
switch (formElement.nodeName.toLowerCase()) {
case 'input':
switch (formElement.type) {
case 'text':
case 'hidden':
case 'password':
case 'button': // Not submitted when submitting form manually, though jQuery does serialize this and it can be an HTML4 successful control
case 'submit':
case 'email':
addNameValue(formElement.name, formElement.value);
break;
case 'checkbox':
case 'radio':
if (formElement.checked) {
addNameValue(formElement.name, formElement.value);
}
break;
case 'file':
// addNameValue(formElement.name, formElement.value); // Will work and part of HTML4 "successful controls", but not used in jQuery
break;
case 'reset':
break;
}
break;
case 'textarea':
addNameValue(formElement.name, formElement.value);
break;
case 'select':
switch (formElement.type) {
case 'select-one':
addNameValue(formElement.name, formElement.value);
break;
case 'select-multiple':
for (j = 0, jLen = formElement.options.length; j < jLen; j++) {
if (formElement.options[j].selected) {
addNameValue(formElement.name, formElement.options[j].value);
}
}
break;
}
break;
case 'button': // jQuery does not submit these, though it is an HTML4 successful control
switch (formElement.type) {
case 'reset':
case 'submit':
case 'button':
addNameValue(formElement.name, formElement.value);
break;
}
break;
}
}
return q.join('&');
}
Comment Form
ReviewManager
ReviewManager
3 Comments
You should use the form element's action attribute instead of window.location.href (it is a more generic solution). as the form may post to some other php script.
When you say "can't get it to work". Do you have some more info? Is there an error message in the console (press F12 on chrome), or anywhere else?
Hi, I can't get it to work, do You have working demo?
Where I have to put script i or at the end of document?