AJAX Part 1

(A quick note: the repo associated with this series is at https://github.com/NerdcoreSteve/vanilla-ajax)

I've blogged about how to set up a web server with Node and Express, routing, running modern JavaScript in the browser, and dynamically modifying a page with JavaScript.

Another piece of making web applications is getting information from the server without having to refresh the page. For that we need AJAX.

AJAX stands for Asynchronous JavaScript And XML, but the XML data format isn't recommended anymore. Most folks use JSON these days.

I don't know why the acronym AJAJ hasn't taken off...

The idea here is that the JavaScript on our web page makes a request from of the web server and then, when the response arrives, your page does something with that new information.

It's important to remember that this is asynchronous. The response will not come back immediately, so we need to write our code understanding that there will be a wait time.

Also, there's always the chance that something might go wrong. So we'll need to write code to handle errors.

Ok, enough talk, let's write some code!

Here's our server-side index.js:

var express = require('express')  
var app = express()  
app.use(express.static('public'))  
app.set('view engine', 'pug')

app.get('/', function (req, res) {  
    res.render('index')
})

app.get('/ajax', function (req, res) {  
    res.json({
        message: 'This comes from an AJAX call!'
    })
})

app.listen(3000, function () {  
    console.log('Example app listening on port 3000!')
})

Note that I've set a regular route for our page, namely "/". and then there's the route for our AJAX call, "/ajax", which responds with a bit of JSON data.

Here's our web pug template:

html  
    head
        title AJAX
    body
        h1(id='message') You haven't clicked it yet
        button(id='clickit') click it
    script(src='js/main.js')

We've got an h1 element that we'll use to display our message from the AJAX call and a button we'll use to initiate that call.

Here's our JavaScript:

var ajax_call = function () {  
    var request = new XMLHttpRequest()

    request.open('GET', '/ajax', true)

    var failure = () =>
        console.error("Something's gone wrong")

    request.onload = function () {
        if (this.status >= 200 && this.status < 400) {
            var data = JSON.parse(this.response)
            document
                .querySelector('#message')
                    .innerHTML =
                        data.message
        } else {
            failure()
        }
    }

    request.onerror = failure

    request.send()
}

document.querySelector('#clickit').onclick = ajax_call  

When you click on the button a new XMLHttpRequest object is created. We then tell this new object that we're going to make a GET request (as opposed to POST, PUT, etc).

Next, we create a function that's used in case of failure. Now in a real application, it's not a good idea to just spit an error to the console. It's better to handle failure more gracefully, like with an error message that a user can understand, but for now console.error is ok.

Next we set request.onload to a function that handles our message. This function gets called after we get our response back from the server. If the response is good, we go ahead and set the text of the h1 tag using the JSON we got back, if not, we run that error log.

At the moment, the big problem with our ajax_call function is that it only does one thing. What if we wanted to do something else? Let's make a function that can handle different types of requests:

var ajax_call = options =>  
    () => {
        var request = new XMLHttpRequest()

        request.open(
            options.request_type,
            options.url,
            true)

        request.setRequestHeader(
            'Content-Type',
            'application/x-www-form-urlencoded;'
                + 'charset=UTF-8')

        request.onload = function () {
            if (this.status >= 200
                && this.status < 400) {

                var data = JSON.parse(this.response)
                options.success(data)
            } else {
                options.failure()
            }
        }

        request.onerror = options.failure

        if(options.data) {
            request.send(
                "data=" + JSON.stringify(options.data))
        } else {
            request.send()
        }
    }

ajax_call now takes an options parameter. It expects options to have the properties request_type, url, success, failure, and optionally data.

request_type is a string indicating what type of HTTP request we're doing (e.g. POST, GET, etc).

url is the url we're sending our request to.

success is the callback function that will be called if the request is successful.

failure is the callback that gets called if the request is not successful.

If data is included, it will be passed along with the request, this is only useful for requests like POST or PUT.

Since AJAX requests sent with XMLHttpRequests require data to be sent as a string with it's keys and values set like so "key=value&key2=value2&key3=value3", we simply stringify the JSON we send back and assign it to the key "data":

"data=" + JSON.stringify(options.data)

Here's a bit of code that attaches to a button:

document.querySelector('#clickthis').onclick =  
    ajax_call({
        request_type: 'POST',
        url: '/ajax',
        failure: () =>
            console.error('Something\'s gone wrong'),
        success: data => 
            document
                .querySelector('#message')
                    .innerHTML = data.message,
        data: {
            message: 'Your mom is a beehive',
            banana: 'elbows'
        }
    })

And here's the JavaScript on the server that handles this request:

app.post('/ajax', function (req, res) {  
    var data = JSON.parse(req.body.data)
    res.json({
        message:
            `This comes from an AJAJ call! `
            + `The message sent to the server `
            + `was ${data.message}! `
            + `The banana was ${data.banana}.`
    })
})

When we click on the clickthis button, our h1 element displays as "This comes from an AJAJ call! The message sent to the server was Your mom is a beehive! The banana was elbows."

I'm sure that, by this point, a lot of you are thinking "why go to all this trouble to write this function when a more robust version of it is built into JQuery?"

That's fair. This solution is not the bullet proof solution offered by JQuery. Different browsers (and their different versions) have their quirks and this function probably won't work in all of them.

Stay tuned for more posts about AJAX soon. We'll look at ways of making this code work in most browsers as well as at other libraries in detail.

Looking for a software developer?