Javascript XMLHttpRequest AJAX Example Form Submit With No Jquery
Published:
By: Kevin from CipherSanctum.comWhat is AJAX and an XMLHttpRequest??
AJAX stands for "Asynchronous Javascript And XML". Using this lets you get information from your server without having to reload the whole page. The information you get can then be loaded into any spot on your page. Using this makes everything more quick, smooth, professional, and makes you the coolest kid on the playground.
What is an XHR??
It's the common abbreviation for an XMLHttpRequest, and it is vanilla Javascript's version of jQuery's way of doing AJAX. Using it isn't hard. All you need is a decent explanation. I'll give you that below.
Why you should use an XMLHttpRequest instead of jQuery
If you choose to use jQuery for one or two small things, it's like using a sledgehammer to kill a mosquito. Using XMLHttpRequest instead of jQuery saves on downloading time, which lets your server and users have a faster experience. If you have a million users on your site, that's about 100kb of extra stuff to download per user. So I suggest you only use jQuery if you make heavy use of manipulating the DOM, and you need it ALL THE TIME. Otherwise, you can enjoy the increased user speed by getting rid of it.
Examples with Python / Django, or any other MVC framework
Note that even though this is shown for Python and Django, the method is still 99.999% the same with any MVC framework.
GET request example with JSON response and a User object
<!-- your HTML template -->
<form method="POST" action="{% url 'signup' %}">
{% csrf_token %}
.....
<input id="id_username" type="text" name="username" required>
.....
</form>
# views.py, with url as {% url 'check_username_django' %}
def check_username_django(request):
username = request.GET.get('username', None).lower()
your_name = {
'is_taken': User.objects.filter(username__iexact=username).exists() # a boolean value: True/False
}
return JsonResponse(your_name)
<!-- Javascript inside your HTML template -->
<script>
var xhr = new XMLHttpRequest(); // *****1*****
var username = document.getElementById('id_username'); // *****2*****
function checkUsername() {
xhr.open('GET', '{% url "check_username_django" %}?username=' + username.value, true); // *****3*****
xhr.send(); // *****4*****
xhr.onload = function() {
if (xhr.readyState == 4 && xhr.status == 200) { // *****5*****
var your_name = JSON.parse(xhr.response) // *****6*****
if (your_name['is_taken']) { // *****7*****
alert("That username already exists.");
}
}
}
}
username.onblur = checkUsername
<script>
Explanation
- A new XMLHttpRequest object must first be instantiated. XHR is often the abbreviation.
- Get your HTML input element by it's id so we can check the username someone enters.
- Take the xhr request object, make it a GET request, make the url a string, and make it asynchronous (true). Since this is a Python / Django example, if you're using another framework you can add any url you need here as a string.... And since this is a GET request, you have the option of adding extra url parameters here as I show. Remember that all GET requests have any extra parameters shown in the url anyway, such as: website.com/some_url?var1=value1&var2=value2. So remember that GET requests are for info that is not dangerous if anyone else sees it. If you print(request.GET) in your view with those parameters, you're going to get <QueryDict: {'var1': ['value1'], 'var2': ['value2']}>
- This is where the request with it's data is sent to the url you make in step 3. If you entered your name as "john", then left the input so the onblur event fired, if you print(request.GET) in your view you would see <QueryDict: {'username': ['john']}>, because the url parameters go to your-website.com/check_username_django?username=john.
- If the xhr is loaded and ready with a 200 http status code, not a 3XX redirect / multiple choice, or a 4XX error...
- Since the function returns a JsonResponse, the response is going to be JSON formatted data. That is similar to a Python dictionary, but was sent back to the user as JSON. So to check it's value with Javascript, we have to parse / convert the response from JSON syntax back into Javascript syntax so we can make comparisons.
- Now we can check the key/value pairs with Javascript syntax. In this case, we only have 1 key/value pair, and the only information in the object returned is: {"is_taken": true}, or false - a boolean. And if it evaluates to true, meaning that it's taken: you get alerted. If the name isn't taken, it evaluates to false, so you get no alert, meaning the name isn't taken.
POST request example with radio button selection and form submit
This one requires using the split method of the XHR responseText property.
Summary: The return value from the xhr you send will be one very long string of jumbled html. So you don't load the entire page onto your current page, you have to split this string from the containing tag before the container you want, and again at the closing element. It will make more sense as you read along.
<!-- your HTML form -->
.....
.....
<div id="vote_container">
<div id="vote_content">
<form method="POST" id="vote_form" action="{% url 'vote' %}">
{% csrf_token %}
.....
{% for choice in question.choice_set.all %}
<div>
<input type="radio" name="choice" value="{{ choice.id }}">
<label>{{ choice.choice_text }}</label>
</div>
{% endfor %}
.....
</form>
</div>
<div>
.....
.....
<!-- your HTML result template to get AJAX from -->
.....
.....
<div id="vote_results_container">
<div id="ajax_vote_results">
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} has {{ choice.votes }}</li>
{% endfor %}
</ul>
</div>
</div>
.....
.....
<!-- Javascript inside the bottom of the page with your form -->
<script>
var vote_container = document.getElementById('vote_container');
var form = document.getElementById('vote_form');
var choices = document.getElementsByTagName('input'); // *****1*****
var choice = '';
var csrf_token = choices[0].value;
var loading = '<h2 class="loading">Loading...</h2>';
function findSelection() {
for (var i = 0; i < choices.length; i++) {
if (choices[i].checked === true) {
choice = choices[i].value; // *****3*****
}
}
}
form.oninput = findSelection // *****2*****
function sendRequest(e) {
e.preventDefault(); // *****5*****
var xhr = new XMLHttpRequest(); // *****6*****
xhr.open('POST', '{% url "vote" %}', true);
xhr.onload = function(e) { // When it's fully loaded, run this
if (this.readyState == 4 && this.status == 200) { // *****11*****
vote_container.innerHTML = xhr.responseText.split('<div id="vote_results_container">')[1].split('</div>')[0];
} else {
vote_container.innerHTML = '<h2>There was an error. Reload the page to try again.</h2>';
}
}
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); // *****7***** // necessary for POST requests.
xhr.send(`choice=${choice}&csrfmiddlewaretoken=${csrf_token}`); // *****8***** // send the value as url parameters (POST)
vote_container.removeChild(vote_container.children[0]) // *****9*****
vote_container.innerHTML = loading; // *****10*****
}
form.onsubmit = sendRequest // *****4*****
</script>
Explanation
- Select all your tags and declare your variables. Here we get the containing element, the form, ALL <input> elements including our csrf_token, an empty choice, and make some HTML to insert into the page while it's loading.
- Everytime the user clicks a radio button we fire an oninput event handler to check which button was selected. This event handler fires every time the input is CHANGED. Not every time it's clicked. So you should only use this event handler if you intend for the person to use this form once. If that's the case, you ought to include sessions too, but for simplicity I didn't include that. And for testing purposes you might want to change the event handler to onclick. Since oninput fires upon CHANGING the input, you will get return errors by testing the same value unless you select a new radio button, and select back. Changing this event handler to onclick prevents this.
- This findSelection() function then loops through all possible input elements, sees which one is checked, and changes the empty choice variable into a new value. Which in this case is a number. Always remember to console.log(your_value); to debug things as you need.
- When the form is submitted, the onsubmit eventhandler fires the sendRequest(e) function.
- This function takes an "e" argument, which is short for the event this function fired from. So the first thing we want to do with this event is prevent this form from loading into the new page with a full browser refresh.
- Then we instantiate a new XMLHttpRequest object, set it's open() method to POST, make the url it goes to as a string, and true for asynchronous.
- Now we set your HTTP header with setRequestHeader to the value shown. If you don't set your POST request header to this value, you'll get errors.
- Now we use the send() method with the data we collected earlier from our choices and choice variables. Sort of like the GET url parameters I showed in the above example, you must put any POST data here in a similar manner. If you print(request.POST) in your view with those parameters, you're going to get something like <QueryDict: {'choice': ['1'], 'csrfmiddlewaretoken': ['a_long_randomized_jumbled_string']}>. And because it's a POST, none of this data sent is visible in the url. Rather, it's hidden. I am also using back ticks, or the tilde button ---> ` <--- to make this. This lets me make what's called a template literal in Javascript, which is essentially the same thing as string formatting in Python, except it's created with back ticks and ${variable} instead of {optional_or_positional_variable} with a .format() at the end. All data is also sent as a string, very similarly to the GET example above. Don't add any question marks. Just `model_attribute=${javascript_variable}&csrfmiddlewaretoken=${javascript_variable_for_csrf_token}`. Add more & symbols for extra parameters if you need.
- Now we remove the first child of the vote_container. Remember that you can always console.log(your_value); to see what child elements you need to remove. Look for the children attribute. That attribute is usually the most common and easiest to dig through.
- Change it's innerHTML to the loading variable we made above just in case your server is overloaded, and the user wants to see something. I gave mine a class that has a CSS animation/transition associated with it.
- Here's the big part... When this xhr loads, we first check to see if it's fully loaded with a 200 http status (see link above for status codes). If it's 200, we actually get one giant jumbled up string of the entire HTML page in xhr.response and xhr.resposeText. The easiest way to get only the portion of the HTML we want is to use Javascript's .split() method. Splitting this will give us an array (or a list in Python terms), which also makes this indexable, and removes the portion we split from. We could also use regular expressions for this, but splitting is much easier... So I first split it from the element of <div id="vote_results_container"> with an index of 1. This means that div element I just typed is gone from the array, and everything before that tag is at the 0 index, meaning all the images, nav bar, and everything included - you don't want that stuff. But this 1 index is now our <div id="ajax_vote_results"> tag. You can see this for yourself by adding this code into your script: console.log(xhr.responseText.split('<div id="vote_results_container">'));. Since we're currently at the 1 index of this array, we now split this 1 index again at the closing div, which is it's 0 index. And we set the innerHTML of this element to be the value of our returned data.
And you're done! You can check out an example of it on my poll app page. I hope this helps someone.
If anyone ever needs a website or some web apps - maybe the next Patreon alternative or Facebook - feel free to contact me!
--Kevin