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 JavaScript và XML. Đ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ẻ div
có id
là result
.
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()
là 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 readyState
là 4
(xem danh sách bên dưới) và status
là 200
, 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()
và 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()
và innerHTML
để cập nhật nội dung mới cho thẻ div
có id
là result
.
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àmgetElementsByClassName()
để 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: name
và age
. 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ẻ div
có id
là result
để 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.txt
là Hello 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ó id
là result
, 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.