Memakai XMLHttpRequest Di JavaScript


Microsoft terkenal dengan strategi embrace, extend and extinguish (http://en.wikipedia.org/wiki/Embrace,_extend_and_extinguish). Contoh yang nyata adalah bagaimana pada awalnya Microsoft ingin mengimplementasikan Java dan menunjukkan bahwa mereka mendukung Java. Namun pada akhirnya, Microsoft membuat sebuah bahasa baru bernama C# dengan menambahkan banyak fitur yang tidak ada di Java (dari pendukung menjadi kompetitor). Begitu juga dengan browser. Pada awalnya, Microsoft Internet Explorer mendukung standar W3C. Lalu, Internet Explorer berkembang diluar jalur dan memperkenalkan banyak fitur baru yang menarik namun tidak compatible dengan browser merk lain. Penggunaan fitur di luar standar tersebut akan membuat developer terikat pada Internet Explorer. Salah satu fitur yang dulunya hanya ada di IE adalah XMLHttpRequest (XHR). Beruntungnya, XHR segera ditiru oleh browser lain dan kini jadi bagian dari standar W3C (http://www.w3.org/TR/XMLHttpRequest2/).

XHR merupakan API penting untuk mengimplementasikan AJAX. Sebagai contoh, saya memiliki HTML seperti berikut ini:

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'/>
    <title>Latihan</title>
    <style type='text/css'>
        body {
            background: linear-gradient(to bottom, #ffffff 0%, #feedb7 50%, #fed452 100%);
        }
        #login {
            border: solid 1px #527dff; margin: 10px; padding: 5px;
            background-color: #ccd9ff;  width: 250px; border-radius: 15px;
            box-shadow: 1px 1px 10px #b8c9ff;       
        }
        #frmLogin div {
            margin: 10px;
        }
        #content {
            margin-top: 30px; padding: 10px; height: 400px;         
        }
    </style>
</head>
<body>    
    <div id='login'>
        <form id='frmLogin'>
            <div>
                <label for='namaUser'>Nama:</label>
                <input type='text' name='namaUser' required />                
            </div>
            <div>
                <label for='password'>Password:</label>
                <input type='password' name='password' required />
            </div>
            <div>
                <input type='submit' value='Login' />
            </div>
        </form>
    </div>
    <div id='content'>
        <h1>Ini adalah halaman utama berisi berita!</h1>
    </div>
</body>
</html>

Halaman ini terdiri atas sebuah area untuk melakukan login dan area lain yang berisi berita, seperti yang terlihat pada gambar berikut ini:

Tampilan awal HTML

Tampilan awal HTML

Dengan XHR, saya dapat mengirimkan data login ke server tanpa harus memperbaharui seluruh layar yang ada. Sebagai contoh, saya akan membuat sebuah controller Laravel sederhana yang mengembalikan JSON dan didaftarkan pada public\login (method POST) yang isinya seperti berikut ini:

<?php

class UserController extends BaseController {

    public function login() {
        $hasil = false;     
        if (Input::has('namaUser') && Input::has('password')) {
            $nama = Input::get('namaUser');
            $password = Input::get('password');
            if ($nama=='solid' && $password=='snake') {
                $hasil = true;
            }                   
        }
        return json_encode(array('hasil' => $hasil, 'nama'=> $nama));
    }

}

?>

Pada kode program PHP di atas, saya mengembalikan string dalam format JSON. Function json_encode() dari PHP akan menerjemahkan array menjadi JSON. Walaupun XmlHttpRequest mengandung nama XML, yang lebih sering digunakan sebagai format pertukaran data saat ini adalah JSON karena lebih mudah diolah oleh pembacanya (bukan sentimen pada Microsoft selaku pembuat XML🙂 ).

Untuk memanggil controller di atas melalui XHR, saya akan menambahkan JavaScript berikut ini pada HTML:

var form = document.getElementById('frmLogin');
var submit = form.querySelector("input[type='submit']");

submit.addEventListener('click', function(e) {
    var xhr = new XMLHttpRequest();
    xhr.open('POST', '/latihan_laravel/public/login', false);
    xhr.send(new FormData(form));
    if (xhr.status==200) {              
        var json = JSON.parse(xhr.responseText);
        if (json.hasil) {
            document.getElementById('login').textContent = 'Selamat datang, ' + json.nama;                  
        } else {
            alert('User atau password salah, ulangi lagi!');
        }
    }
}, false);

Pada JavaScript di atas, saya menggunakan XHR untuk mengirim data melalui method POST secara synchronous ke server. Mengirim data dalam bentuk POST tidak mudah, oleh sebab itu XMLHttpRequest Level 2 memperkenalkan FormData() yang dapat melakukan encoding <form> secara otomatis sehingga siap dikirim untuk POST.

Saya dapat membaca hasil yang dikembalikan oleh server melalui property responseText. Karena pada PHP, saya mengirim hasil dalam bentuk JSON, maka di JavaScript, saya dapat menggunakan JSON.parse() untuk menerjemahkan string dalam bentuk JSON menjadi object JavaScript. Hampir semua browser modern sudah dilengkapi dengan object JSON. Pada browser yang tidak memiliki object JSON, saya bisa langsung memanggil eval() untuk mengubah string yang dikembalikan dari server menjadi object JavaScript.

Setelah itu, bila login berhasil, maka saya akan menganti form login dengan tulisan ‘Selamat datang’ seperti yang terlihat pada gambar berikut ini:

Request ke server tanpa memperbaharui seluruh halaman

Request ke server tanpa memperbaharui seluruh halaman

Bila saya ingin pemanggilan dilakukan secara asynchronous dimana send() tidak menunggu hingga respon diterima melainkan langsung lanjut ke perintah berikutnya, saya dapat mengubah JavaScript di atas menjadi seperti berikut ini:

submit.addEventListener('click', function(e) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
        if (xhr.status==200) {              
            var json = JSON.parse(xhr.responseText);
            if (json.hasil) {
                document.getElementById('login').textContent = 'Selamat datang, ' + json.nama;
            } else {
                alert('User atau password salah, ulangi lagi!');
            }
        }   
    }
    xhr.open('post', '/latihan_laravel/public/login', true);
    xhr.send(new FormData(form));
    e.preventDefault();
}, false);

Event load adalah salah satu event yang diperkenalkan oleh XMLHttpRequest Level 2. Event ini akan terjadi bila respon dari server sudah diterima seluruhnya. Karena saya melakukan request secara asynchronous, maka saya saya perlu menambahkan event handler untuk load dimana ia akan dikerjakan hanya setelah respon diterima seluruhnya.

Perhatikan bahwa tidak terjadi perpindahan halaman selama saya memanggil server melalui XHR. Hal ini dapat terlihat dari tombol back yang tidak aktif. Bila seandainya ingin memberikan sebuah entry virtual di history sehingga user bisa men-klik tombol back, saya dapat menambahkan penggunaan HTML5 History API seperti berikut ini:

...
submit.addEventListener('click', function(e) {
    ...
    if (xhr.status==200) {              
        var json = JSON.parse(xhr.responseText);
        if (json.hasil) {
            document.getElementById('login').textContent = 'Selamat datang, ' + json.nama;
            history.pushState({login: json.nama}, "Login", '/latihan_laravel/public/latihan_xhr.html');                 
        } else {
            alert('User atau password salah, ulangi lagi!');
        }
    }
}, false);

window.addEventListener('popstate', function(e) {         
    if (e.state==null) {
        window.location.reload();
    } else if (e.state.login) {
        document.getElementById('login').textContent = 'Selamat datang, ' + e.state.login;
    }
}, false);

Sekarang, bila login berhasil, saya akan memperoleh sebuah entry di history seperti yang terlihat pada gambar berikut ini:

Membuat history semu dengan HTML5 History API

Membuat history semu dengan HTML5 History API

Dengan demikian, saya bisa berpindah antar-history walaupun saya tidak pernah memperbaharui layar secara keseluruhan.

Salah satu keterbatasan XHR adalah ia hanya bisa dipakai untuk memanggil URL pada domain yang sama. Bila saya berusaha memanggil URL yang di-host pada domain berbeda, Firefox akan menampilkan pesan kesalahan berikut ini di console:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at [xyz]. This can be fixed by moving the resource to the same domain or enabling CORS.

Untuk mengatasi masalah ini, W3C memperkenalkan standar Cross-Origin Resource Sharing (CORS). Sebelum CORS, teknik yang sering digunakan adalah image ping (sama seperti yang dipakai untuk mengetahui apakah seseorang telah membuka email kita) dan JSONP. CORS adalah alternatif yang lebih modern dan standar karena browser modern memiliki XMLHttpRequest yang mendukung CORS. CORS bekerja dengan menggunakan HTTP header. Sebagai contoh, saat mengakses URL pada domain yang berbeda, browser dengan XMLHttpRequest yang mendukung CORS akan menambahkan header Origin seperti yang terlihat pada gambar berikut ini:

HTTP Header yang dibuat XMLHttpRequest bila memanggil URL pada domain berbeda

HTTP Header yang dibuat XMLHttpRequest bila memanggil URL pada domain berbeda

Tanggung jawab browser sampai disini, bagian berikutnya yang lebih penting adalah peran server dalam mendukung CORS. Server harus menjawab respon dengan menyertakan HTTP header bernama Access-Control-Allow-Origin yang minimal sesuai dengan isi Origin atau * bila server mengizinkan semua domain untuk mengaksesnya. Sebagai contoh, saya dapat mengubah kode program PHP (Laravel) di server menjadi seperti berikut ini:

<?php

class UserController extends BaseController {

    public function login() {
        $origin = Request::header('Origin');
        $hasil = false;     
        if (Input::has('namaUser') && Input::has('password')) {
            $nama = Input::get('namaUser');
            $password = Input::get('password');
            if ($nama=='solid' && $password=='snake') {
                $hasil = true;
            }                   
        }
        return Response::json(array('hasil' => $hasil, 'nama'=> $nama), 200, 
            array('Access-Control-Allow-Origin'=>$origin));
    }

}

?>

Bila sebelumnya saya membuat JSON secara manual dengan function PHP json_encode(), maka kali ini saya memakai Response::json() yang merupakan facade Laravel yang akan menghasilkan sebuah JsonResponse (turunan dari \Symfony\Component\HttpFoundation\JsonResponse). Pada facade Response::json(), argumen pertama adalah objek yang hendak dikonversikan menjadi JSON. Argumen kedua adalah HTTP status. Pada argumen ketiga, saya bisa mengisi HTTP header yang hendak dikembalikan ke browser.

Sekarang, bila saya mengerjakan kode program JavaScript yang melakukan request melalui XHR, semua akan baik2 saja walaupun URL yang dipanggil berada pada domain berbeda. Hal ini karena saya sudah mengikuti CORS melalui HTTP header yang terlihat seperti pada gambar berikut ini:

Request dari XHR dan response dari server yang mendukung CORS

Request dari XHR dan response dari server yang mendukung CORS

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: