Ajax với JavaScript và jQuery

Trước khi tìm hiểu về Ajax, ta hãy bàn về quy trình gửi request và nhận response giữa trình duyệt (client) và server. Thông thường, khi duyệt đến địa chỉ một trang web, trình duyệt sẽ gửi một request đến địa chỉ đó. Khi phía server nhận được request này, nó sẽ trả về một response để trình duyệt hiển thị ra màn hình. Response thường là mã HTML mà trình duyệt có thể hiểu và thông dịch thành các thành phần trên trang web.

Nếu muốn thay đổi một phần tử trên trang web, tôi phải gửi một request mới đến server và nó sẽ trả về response với toàn bộ nội dung của trang bao gồm phần tử mà tôi muốn cập nhật nội dung mới. Như vậy, trình duyệt phải tải lại cả trang, trong khi tôi chỉ muốn cập nhật một phần tử duy nhất.

Ajax giúp khắc phục nhược điểm này. Thay vì phải tải lại trang để cập nhật một phần tử HTML, ta chỉ tải nội dung cần được cập nhật mà thôi. Ajax không phải là công nghệ mới mà là sự kết hợp giữa nhiều công nghệ cũ, trong đó bao gồm JavaScriptXML. Điều này được phản ánh trong cái tên của nó: Asynchronous JavaScript and XML. Để hiểu rõ hơn về Ajax, ta hãy xem xét từng từ trong cái tên dài ngoằng này.

Đầu tiên là từ Asynchronous, nghĩa là “bất đồng bộ”. Điều này nghĩa là request có thể được gửi kể cả khi trang được tải hoàn tất và hiển thị ra trình duyệt. Ngoài ra, ứng dụng không phải chờ đợi response từ server. Thay vào đó, ứng dụng có thể làm việc khác, khi nào response tới thì sẽ xử lý. Điều này giúp ứng dụng hoạt động trơn tru mà không bị treo khi chờ đợi một tác vụ chiếm nhiều thời gian. Từ thứ hai là JavaScript. Đây là một thành phần quan trọng vì JavaScript sẽ được dùng để gửi request và biến hóa các thành phần trên trang để hiển thị nội dung mới nhận từ server. Từ cuối cùng là XML, đây là từ gây nhiều hiểu lầm nhất về Ajax vì nó khiến người ta nghĩ Ajax chỉ làm việc với XML. Thực chất, Ajax có thể làm việc với tất cả dữ liệu dạng text: HTML, XML hoặc JSON.

Dùng Ajax với dữ liệu plain text

Trái với suy nghĩ của nhiều người, Ajax thực sự rất đơn giản nếu ta hiểu quy trình làm việc với nó. Trước tiên, để thực hành ví dụ này, bạn phải có một local server. Bạn có thể download ứng dụng local server mà bạn thích, ở đây tôi sẽ dùng XAMPP. Tiếp theo, tôi tạo một file HTML có nội dung sau:

1
2
3
4
5
6
7
8
9
10
<html>
<head>
    <meta charset="utf-8" />
    <title>Ajax</title>
</head>
<body>
<div id="result"></div>
<script></script>
</body>
</html>

Nội dung file HTML khá đơn giản. Nó chỉ gồm một thẻ div để hiển thị nội dung response từ server và một cặp thẻ script để viết JavaScript. Trong thẻ script, tôi sẽ viết code để gửi request và xử lý response từ server.

1
2
3
4
var xhr = new XMLHttpRequest();
xhr.open("GET", "data.txt", false);
xhr.send();
document.getElementById("result").innerHTML = xhr.responseText;

Tiếp theo, ta tạo file data.txt chứa nội dung text, ở đây tôi ghi vào dòng chữ Hello World và lưu file này cùng cấp với file HTML. Cuối cùng, tôi bật trình duyệt, truy cập localhost và thấy ngay dòng chữ Hello World. Như vậy, ứng dụng Ajax đầu tiên đã thành công mỹ mãn. Bây giờ, ta sẽ xem xét chi tiết từng câu lệnh JavaScript trong ví dụ trên.

Trước tiên, ta tạo đối tượng XMLHttpRequest. Đối tượng này là trái tim của Ajax, mọi thao tác gửi request và nhận response sẽ thông qua đối tượng này. Ở dòng số 2, ta mở một kết nối tới server thông qua hàm open(). Hàm này nhận 3 tham số. Tham số thứ nhất tôi đưa vào chuỗi “GET” để thông báo rằng tôi muốn dùng phương thức GET. Tham số thứ 2 là đường dẫn tới file cần lấy nội dung, trong ví dụ này là data.txt. Tham số sau cùng là tham số kiểu boolean. Nếu là true, request gửi đi sẽ bất đồng bộ (asynchronous). Còn nếu là false thì nó sẽ đồng bộ (synchronous). Sự khác nhau giữa hai khái niệm này rất đơn giản. Đồng bộ nghĩa là trình duyệt sẽ đợi cho tới khi nhận được tất cả reponse rồi mới thực hiện câu lệnh tiếp theo. Nói cách khác, trình duyệt sẽ ngồi chờ cho tới khi thực hiện xong tác vụ này rồi mới tiếp tục tác vụ khác. Với bất đồng bộ, trình duyệt không phải ngồi đợi, thay vào đó, nó sẽ thực hiện song song nhiều công việc cùng lúc. Do vậy, trình duyệt sẽ không bị rơi vào tình trạng đóng băng do chờ đợi một tác vụ nào đó hoàn thành.

Ở dòng thứ 3, tôi tiến hành gửi request bằng hàm send() của đối tượng XMLHttpRequest. Sau cùng, tôi hiển thị nội dung reponse nhận được từ server thông qua thuộc tính responseText và xuất ra màn hình trong một thẻ dividresult.

Sức mạnh của Ajax đến từ việc nó có thể thực hiện tác vụ bất đồng bộ. Do đó, khi dùng Ajax, tôi thường để tham số thứ 3 của hàm open()true để tận dụng sức mạnh asynchronous. Thậm chí tôi không cần truyền vào tham số này vì mặc định nó có giá trị true.

Đối tượng XMLHttpRequest cung cấp vài event để dùng trong những trường hợp đặc biệt. Trong đó, ta sẽ tập trung vào event onreadystatechange. Như tên gọi của nó, event sẽ chạy khi trạng thái của request thay đổi. Sử dụng event onreadystatechange rất quan trọng khi dùng bất đồng bộ vì ta không biết khi nào thì response sẽ được nhận. Do đó, trong ví dụ tiếp theo, tôi sẽ chỉnh sửa đoạn code để tận dụng sức mạnh bất đồng bộ:

1
2
3
4
5
6
7
8
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        document.getElementById("result").innerHTML = xhr.responseText;
    }
};
xhr.open("GET", "data.txt");
xhr.send();

Ở ví dụ này, tôi viết event handler cho onreadystatechange. Vì event sẽ chạy mỗi khi trạng thái request thay đổi nên tôi kiểm tra để chắc rằng readyState4 (xem danh sách bên dưới) và status200, nghĩa là đã nhận được response thành công. Nếu như vậy, tôi ghi nội dung response ra màn hình. Lưu ý là tôi không truyền giá trị vào tham số thứ 3 trong hàm open() để sử dụng giá trị mặc định true của nó, nghĩa là dùng bất đồng bộ.

Đây là danh sách 5 trạng thái của request chứa trong thuộc tính readyState:

  • Giá trị 0: hàm open() chưa được gọi.
  • Giá trị 1: hàm send() chưa được gọi.
  • Giá trị 2: hàm send() đã được gọi.
  • Giá trị 3: đang tải dữ liệu, responseText chứa một phần dữ liệu.
  • Giá trị 4: mọi thứ đã hoàn tất.

Kỹ thuật xử lý nội dung

Thông thường, ta dùng Ajax để tải thông tin và cập nhật nó vào một phần nhỏ của trang web. Điều này xảy ra sau khi trang đã được nạp hoàn chỉnh trong trình duyệt. Do vậy, để thay đổi các thành phần trong trang sau khi đã được tải xong, bắt buộc ta phải dùng các hàm xử lý DOM trong JavaScript, và hàm mà ta sẽ dùng nhiều nhất khi dùng Ajax chính là getElementById(). Bên cạnh đó, ta cũng sẽ dùng thuộc tính innerHTML để thay đổi nội dung các phần tử HTML. Việc dùng kết hợp getElementById()innerHTML rất phổ biến trong Ajax. Do đó, nếu bạn chưa quen với cách dùng hai tính năng này, tôi khuyên bạn hãy dành ra một chút thời gian để làm quen với chúng.

1
2
3
4
5
6
7
8
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        document.getElementById("result").innerHTML = xhr.responseText;
    }
};
xhr.open("GET", "data.txt");
xhr.send();

Ở dòng 4, tôi kết hợp getElementById()innerHTML để cập nhật nội dung mới cho thẻ dividresult.

Lưu ý rằng thuộc tính ID của một phần tử phải là duy nhất. Nếu bạn muốn cập nhật nhiều phần tử với cùng một nội dung thì nên chuyển sang dùng thuộc tính class. Sau đó, bạn dùng hàm getElementsByClassName() để lấy ra danh sách tất cả phần tử có thuộc tính class tương ứng. Tiếp theo, bạn dùng vòng lặp để thay đổi nội dung của các phần tử này.

Thỉnh thoảng, ta không muốn truy cập một phần tử mà muốn cả nhóm phần tử tương tự nhau. JavaScript cung cấp hàm getElementsByTagName() để chọn ra những phần tử có tên thẻ giống nhau. Các phần tử này sẽ được gom lại thành một mảng (array) và ta có thể dùng vòng lặp để xử lý từng phần tử. Giả sử tôi có một danh sách như sau:

1
2
3
4
5
6
<ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
</ul>

Để chọn ra tất cả các phần tử li trong danh sách trên, tôi sẽ dùng hàm getElementsByTagName():

1
var listItems = document.getElementsByTagName('li');

Để lấy nội dung của một phần tử <li>, tôi dùng thuộc tính innerHTML:

1
console.log(listItems[1].innerHTML);

Cửa sổ console của trình duyệt sẽ hiển thị nội dung Item 2 của phần tử <li> thứ 2 trong danh sách. Đó là kỹ thuật hay dùng khi làm việc với nhiều phần tử tương tự nhau.

Quay lại ví dụ về Ajax, tôi sẽ cập nhật toàn bộ phần tử trong danh sách bằng cụm từ Hello World lấy ra từ file data.txt trên server bằng đoạn code sau:

1
2
3
4
5
6
7
8
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        document.getElementsByTagName("li").innerHTML = xhr.responseText;
    }
};
xhr.open("GET", "data.txt");
xhr.send();

Lúc này, toàn bộ danh sách sẽ xuất hiện cụm từ Hello World.

Dùng Ajax với dữ liệu XML

Ajax ban đầu dùng để truyền tải dữ liệu dạng XML, đó cũng chính là chữ X trong Ajax. Tuy XML không được ưa thích bằng JSON nhưng nó vẫn là một định dạng phổ biến. Do vậy, ta nên biết cách xử lý kiểu dữ liệu này để không phải bỡ ngỡ khi gặp chúng.

Một file XML có nội dung tương tự file HTML, nghĩa là nó cũng có các cặp thẻ đánh dấu. Tuy nhiên, các thẻ trong XML là do ta tự đặt chứ không được thiết lập sẵn như HTML. Dưới đây là một ví dụ về XML:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<persons>
    <person>
        <name>John</name>
        <age>30</age>
    </person>
    <person>
        <name>Jim</name>
        <age>25</age>
    </person>
    <person>
        <name>Jane</name>
        <age>23</age>
    </person>
</persons>

Tiếp theo, tôi dùng Ajax để tải dữ liệu XML này về. Trước hết, tôi lưu file XML với tên data.xml và đặt trên server. Vì dữ liệu trả về là XML, đối tượng XMLHttpRequest cung cấp thuộc tính responseXML chứa kiểu dữ liệu này. Ta thay đổi đoạn code Ajax như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var names = xhr.responseXML.getElementsByTagName("name");
        var output = "<ul>";
        for (var i = 0; i < names.length; i++) {
            output += "<li>" + names[i].firstChild.nodeValue + "</li>";
        }
        output += "</ul>";
        document.getElementById("result").innerHTML = output;
    }
};
xhr.open("GET", "data.xml");
xhr.send();

Lúc này, kiến thức về xử lý DOM trở nên hữu dụng hơn bao giờ hết. Vì XML có cấu trúc tương tự như HTML nên ta có thể dùng hàm getElementsByTagName() để lấy ra những phần tử mong muốn.

Trong đoạn code ví dụ, tôi lấy ra tất cả thẻ <name> trong dữ liệu XML trả về. Sau đó, tôi dùng vòng lặp for để truy cập lần lượt các phần tử và lấy ra nội dung của chúng. Ở dòng 7, tôi dùng thuộc tính firstChild để lấy ra phần tử con đầu tiên trong phần tử <name>. Tiếp theo, tôi dùng thuộc tính nodeValue để lấy nội dung dạng text của firstChild. Ngoài ra, tôi cũng bọc chúng trong thẻ li để trình duyệt hiển thị dưới dạng danh sách. Tại dòng 10, sau khi đưa tất cả giá trị cần xuất ra màn hình vào biến output, tôi truyền nội dung của biến này vào thẻ div thông qua thuộc tính innerHTML.

Việc xử lý XML có thể phức tạp hơn so với những gì tôi trình bày. Tuy nhiên, về căn bản thì chúng chỉ như thế này. Khi hiểu rõ cách thức xử lý XML, ta có thể dễ dàng mở rộng nó ra và thêm nhiều tính năng cao cấp hơn. Bạn có thể dùng đoạn code ví dụ làm khung sườn để phát triển và mở rộng thêm.

Dùng Ajax với dữ liệu JSON

JSON là định dạng phổ biến nhất khi nói đến truyền dữ liệu giữa server và client. Sỡ dĩ nó được ưa chuộng là vì tính tiện lợi, nhanh chóng và dễ dàng trong việc phân tích và xử lý nội dung. JSON là viết tắt của JavaScript Object Notation, nghĩa là nó dùng cú pháp tương tự như cú pháp khai báo đối tượng trong JavaScript. Khi nhận được dữ liệu dạng JSON từ server, ta chỉ cần gọi một lệnh đơn giản là có thể dùng nó như một đối tượng. Nếu bạn đã từng dùng đối tượng trong JavaScript thì chắc đã biết nó tiện lợi thế nào rồi.

Nếu bạn chưa bao giờ làm việc với JSON, đoạn code sau sẽ cho bạn thấy diện mạo của chúng:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
        "name": "John",
        "age": 23
    },
    {
        "name": "Jane",
        "age": 25
    },
    {
        "name": "Janet",
        "age": 30
    }
]

Ở đây tôi khai báo 3 đối tượng, mỗi đối tượng có 2 thuộc tính: nameage. Tiếp theo, tôi lưu đoạn code trên thành file data.json và chỉnh đoạn code Ajax để xử lý JSON.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var items = JSON.parse(xhr.responseText);
        var output = "<ul>";
        for (var index in items) {
            output += "<li>" + items[index].name + "</li>";
        }
        output += "</ul>";
        document.getElementById("result").innerHTML = output;
    }
};
xhr.open("GET", "data.json");
xhr.send();

Ở dòng số 4, tôi dùng hàm parse() của đối tượng JSON để phân tích nội dung JSON trả về từ server. Sau khi chạy hàm parse(), biến items là một mảng gồm các phần tử là đối tượng như đã khai báo trong file data.json. Tiếp theo, tôi dùng vòng lặp for để lần lượt đi qua phần tử trong mảng và truy xuất thuộc tính name của từng phần tử. Cuối cùng tôi định dạng nó thành một danh sách và xuất ra màn hình tương tự như lần trước.

Mọi thứ chỉ đơn giản có thế. Đây cũng là lí do tôi thích JSON: đơn giản, thuận tiện, nhanh chóng. Nếu bạn đã quen làm việc với đối tượng trong JavaScript thì kiểu JSON chỉ là chuyện nhỏ.

Ajax với jQuery

Khi nói đến JavaScript thì không thể không nói tới cái tên đình đám jQuery. jQuery được tạo ra nhằm để khắc phục những nhược điểm của JavaScript. Đồng thời cũng giúp lập trình viên không phải vò đầu bứt tóc khi trang web chạy rất tốt trên trình duyệt này nhưng lại te tua tơi tả trên trình duyệt khác (Internet Explorer chẳng hạn). Ngoài ra, với jQuery, ta viết ít code hơn và làm được nhiều việc hơn. Từ giờ, bạn sẽ không phải gõ hàm getElementById() dài ngoằng mỗi khi muốn chọn một phần tử HTML.

Để sử dụng jQuery, ta tải về file mã nguồn tại trang web của nó, hoặc dùng trực tiếp link từ CDN (Content Delivery Network) của các ông lớn như Google hay Microsoft. Tôi thích dùng CDN hơn vì chỉ cần dán đường dẫn tới file jQuery là có thể dùng ngay lập tức. Ngoài ra, trong trường hợp các trang khác cũng dùng đường link CDN tương tự thì trình duyệt sẽ tái sử dụng file đã được tải, giúp web chạy nhanh hơn. Sau khi copy link đến jQuery, ta paste nó vào file HTML như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Ajax</title>
</head>
<body>
<div id="result"></div>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script>
    ...
</script>
</body>
</html>

Tôi cũng tạo sẵn một thẻ dividresult để chứa nội dung tải về qua Ajax. Tôi cũng viết sẵn thẻ script ngay bên dưới link jQuery để lát nữa viết code vào trong đó. Tiếp theo, ta cũng cần tạo một file data.txt có nội dung bất kì để nạp vào trong thẻ div bên trên. Ở ví dụ này, tôi sẽ ghi vào nội dung file data.txtHello World.

Với jQuery, làm việc với Ajax không thể dễ hơn được nữa:

1
$("#result").load("data.txt");

Nếu ta chạy file HTML này trong trình duyệt thì sẽ thấy kết quả là dòng chữ Hello World. Những gì dòng code này thực hiện là chọn đối tượng có idresult, sau đó cập nhật nội dung cho đối tượng này bằng nội dung tải về từ file data.txt trên server. Nhìn sơ vẻ bề ngoài, đoạn code trên trông có vẻ đơn giản. Tuy nhiên, phía sau hậu trường, jQuery phải làm việc khá cật lực để lấy dữ liệu từ file data.txt. Hãy tưởng tượng bạn phải viết code JavaScript thuần chỉ để lấy dữ liệu từ file text đơn giản.

Trong trường hợp JSON, jQuery cung cấp một hàm chuyên làm việc với kiểu dữ liệu này: getJSON(). Trước khi bắt đầu code, tôi chuẩn bị file data.json và đặt nó cùng cấp với file HTML trên server. Nội dung của file JSON này như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[
    {
        "name": "John",
        "age": 23
    },
    {
        "name": "Jane",
        "age": 25
    },
    {
        "name": "Janet",
        "age": 30
    }
]

Chuẩn bị đã xong, ta sẽ viết code:

1
2
3
4
5
6
7
8
$.getJSON("data.json", function(data) {
    var output = "<ul>";
    $.each(data, function(index, value) {
        output += "<li>" + value.name + "</li>";
    });
    output += "</ul>";
    $("#result").html(output);
});

Tại dòng 1, ta dùng hàm getJSON() của jQuery để lấy dữ liệu dạng JSON từ file data.json. Sau đó, ta cung cấp cho getJSON() một hàm dùng để xử lý dữ liệu JSON nhận được từ phía server thông qua biến data. Tại dòng số 3, ta lặp qua các phần tử trong mảng data và lấy ra thuộc tính name của các phần tử. Những dòng code còn lại chỉ để định dạng output trước khi xuất ra màn hình tại dòng số 7 với hàm html().

Ngoài hai hàm trên tôi thường dùng, jQuery còn cung cấp thêm một hàm ajax() tổng quát. Với hàm này, ta có thể tùy chỉnh cấu hình, thêm bớt các thông số chứ không bị bó buộc như hai hàm trên. Để sử dụng ajax(), ta làm theo cấu trúc mẫu sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
    type: 'GET',
    url: 'https://hieusensei.com',
    data: {
        data1: "someData1",
        data2: "someData2"
    },
    success: function(data) {
        // Success
    },
    error: function() {
        // Fail
    }
});

Đầu tiên, tôi gọi hàm ajax(). Tiếp theo, tôi truyền vào tham số một đối tượng. Đối tượng này chứa các thuộc tính để cấu hình cho lệnh gửi request. Ở dòng số 2, tôi khai báo kiểu HTTP sẽ dùng, ở đây tôi chọn GET. Tại dòng số 3, tôi khai báo địa chỉ sẽ xử lý request. Lưu ý rằng địa chỉ này phải có cùng domain với trang gửi request, nghĩa là chúng phải có cùng nguồn gốc (same-origin policy). Tuy nhiên, một số server cho phép nhận request xuyên domain (cross-domain request) nên ta có thể dùng ajax() để gửi request không cùng nguồn gốc. Ở dòng số 4, tôi khai báo các thông số đính kèm theo request nếu có. Ở dòng số 8, tôi khai báo hàm để xử lý dữ liệu trả về khi thành công. Cuối cùng, ở dòng 11, tôi khai báo hàm để xử lý khi có lỗi xảy ra.

Lưu ý: đây là cấu trúc tôi thường dùng nhất mà thôi. Ngoài những thông số trên, hàm ajax() còn cung cấp nhiều thông số khác. Bạn có thể tham khảo thêm tại trang API Documentation của jQuery.

Sử dụng lại file data.json, tôi sẽ chế biến đoạn code Ajax trên như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$.ajax({
    type: 'GET',
    url: 'data.json',
    success: function(data) {
        var output = "<ul>";
        $.each(data, function(index, value) {
            output += "<li>";
            output += value.name;
            output += "</li>";
        });
        output += "</ul>";
        $("#result").html(output);
    }
});

Ở đây, tôi chỉ dùng những thuộc tính cần thiết để cấu hình cho request. Đoạn code này khá đơn giản nên tôi không giải thích gì nhiều. Hầu hết các dòng code là để định dạng dữ liệu trước khi xuất ra màn hình.

Một câu hỏi cuối cùng mà tôi muốn trả lời trước khi kết thúc bài viết này: Khi nào thì biết nên dùng hàm nào? Tôi thường dùng hàm load() khi chỉ muốn nạp nội dung dạng Text hoặc HTML thuần túy vào trong một phần tử HTML. Đây là cách ngắn gọn và thuận tiện nhất để làm việc này. Khi xử lý dữ liệu JSON, tôi sẽ dùng hàm getJSON(). Còn khi tôi cần làm việc với kiểu dữ liệu khác hoặc muốn tự tay cấu hình các thông số cho request, tôi sẽ dùng hàm ajax(). Ngoài ra, jQuery còn cung cấp khá nhiều hàm có liên quan tới Ajax, bạn có thể tham khảo thêm trong đề mục Ajax trong API Documentation.