Bài toán cần xử lý
Khi bạn làm việc với Ajax để gửi và nhận giữ liệu chắc hẳn các bạn sẽ có câu hỏi rằng làm sao để bắt được sự kiện khi gửi Request lên Server và làm sao Disabled các chức năng của user trong khi Request đang được xử lý sau mỗi lần Call Ajax. Ví dụ khi bạn gửi một Request để tạo mới một bản ghi nào đó, lúc này bạn muốn hiện trạng thái Loading và Disabled các Button trên giao diện sau khi nhấn Submit là đợi Respose trả về thì lúc này bạn sẽ phải set disabled một cách thủ công các sự kiện. Điều này thật sự không chuyên nghiệp và làm bạn phải mất thêm nhiều thời gian để set thủ công.
Ví dụ cụ thể
Để có cái nhìn tường mình hơn về kỹ thuật này chúng ta cùng đi vào một ví dụ nhỏ sau đây. Mình sẽ tạo môt Web App có tên là Code Mega Todo List với chức năng là là get dữ liệu từ API. Giữ liệu mình lấy từ JSONPlaceholder
Đầu tiên sẽ là phần code giao diện của App
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, shrink-to-fit=no"
/>
<title>Code Mega</title>
<link
rel="shortcut icon"
type="image/x-icon"
href="https://www.code-mega.com/images/theme/favicon.png"
/>
<link rel="stylesheet" href="css/style.css" />
<script src="lib/jquery.js"></script>
<script src="js/before-using-ajax-loading-ui-blocking.js"></script>
</head>
<body>
<div id="overlay" style="display: none;"></div> <!--Important-->
<section class="container">
<div class="heading">
<img
class="heading__img"
src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/756881/laptop.svg"
/>
<h1 class="heading__title">Code Mega To-Do List</h1>
</div>
<button class="button" id="btn-get-more"><span>Get more</span></button> <!--Important-->
<div>
<span id="loading" class="loader" style="display: none;"></span> <!--Important-->
<ul class="toDoList"></ul>
</div>
</section>
</body>
</html>
Code sẽ có giao diện như sau:

#Note: Các bạn chú 3 element chính trong giao diện có id lần lượt là:
overlay: có tác dụng disabled toàn bộ UI của bạn. Nghĩa là bạn sẽ không thể click vào các button hay làm bất cứ điều gì trên giao hiên
btn-get-more: handle sự kiện từ user
loading: hiển thị icon loading trên giao diện
Giao diện sau khi người dùng click button Get More

Gải quyết vấn đề
Tạo các hàm xử lý
Ở đây mình có 2 hàm chính là getList và getMore. Hàm getList sẽ được thực thi khi lần đầu vào Web và sau khi hàm getMore được gọi, còn hàm getMore sẽ được thực thi khi người dùng click vào Button Get More.
getList: Lấy giữ liệu từ API https://jsonplaceholder.typicode.com/posts và hiển thị lên danh sách
getMore: Lấy thêm giữ liệu từ API là thêm vào danh sách
Trước khi xử lý Ajax Loading và Blocking UI
Đầu tiền sẽ là phần code js chưa qua xử lý Ajax Loading và Blocking UI
let total = 1;
let getMore = function () {
$("#btn-get-more").on("click", function () {
$(".toDoList").empty();
total += 1;
getList(total);
});
};
let getList = function (total) {
// Begin set loading and block ui
$("#loading").show();
$("#overlay").show();
$.ajax({
cache: false,
type: "GET",
url: "https://jsonplaceholder.typicode.com/posts",
success: function (data) {
// unset loading and block ui
$("#loading").hide();
$("#overlay").hide();
for (let i = 0; i < total; i++) {
$(".toDoList").append("<li>" + data[i].title + "</li>");
}
},
error: function (data) {
// unset loading and block ui
$("#loading").hide();
$("#overlay").hide();
},
});
};
// Main function
$(function () {
getList(total);
getMore();
});
Như các bạn thấy, khi chúng ta gọi hàm getList thì ở phần trước khi Ajax được call chúng ta phải gọi thêm 2 medthod để xử lý việc hiện Loading và Block UI sau đó chúng ta sẽ ẩn Loading và Block UI đi khi các Respose được trả về. Điều này hoàn toàn đúng logic nhưng bây giờ thử đặt vấn đề là bạn không chỉ có 1 hàm getList mà có các hàm sử dụng Ajax để gửi Request khác như getDetail, add, delete v...v... thì lúc này có phải bạn sẽ phải gọi đi gọi lại 2 method là $("#loading").show() và $("#overlay").show() nhiều lần tại mỗi lần call, đúng chứ? Một pha xử lý rât manual và bất tiện đúng không nào !
Sau khi xử lý Ajax Loading và Blocking UI
Để giải quyết vấn đề trên chúng ta sẽ sử dụng ajaxSend và ajaxComplete, các bạn xem đoạn code sau:
let total = 1;
let counter = 0;
// Handle each reuqest send
$(document)
.ajaxSend(function (event, xhr, options) {
counter++;
//set loading and block ui
if (counter > 0) {
$("#loading").show();
$("#overlay").show();
}
})
.ajaxComplete(function (event, xhr, options) {
counter--;
// unset loading and block ui
if (counter <= 0) {
$("#loading").hide();
$("#overlay").hide();
}
});
let getMore = function () {
$("#btn-get-more").on("click", function () {
$(".toDoList").empty();
total += 1;
getList(total);
});
};
let getList = function (total) {
$.ajax({
cache: false,
type: "GET",
url: "https://jsonplaceholder.typicode.com/posts",
success: function (data) {
for (let i = 0; i < total; i++) {
$(".toDoList").append("<li>" + data[i].title + "</li>");
}
},
error: function (data) {},
});
};
// Main function
$(function () {
getList(total);
getMore();
});
Vẫn là các hàm đó nhưng bây giờ chúng ta sẽ không show/hide thủ công để Block UI như đoạn code trên nữa thay vào đó là việc tự động hanlde sau mỗi lần gọi request bằng Ajax. Điều này được thực hiện trong đoạn code:
.ajaxSend(function (event, xhr, options) {
counter++;
//set loading and block ui
if (counter > 0) {
$("#loading").show();
$("#overlay").show();
}
})
.ajaxComplete(function (event, xhr, options) {
counter--;
// unset loading and block ui
if (counter <= 0) {
$("#loading").hide();
$("#overlay").hide();
}
});
Các bạn để đoạn code này ở hàm common của mình hoặc để nó ở trên head sau đoạn code nhúng JQuery nó sẽ tự động hanlde tất cả các request.
Tới đây thì mọi chuyện đã được xử lý tuy nhiên vẫn còn một vấn đề nhỏ nữa là không phải lúc chúng ta show/hide phần Loading và Block UI ở tất cả các request. Đôi khi có những request được call ngầm và chúng ta không muốn show Loading và Block UI ở các request đó.
Vì vậy chúng cần thêm một đoạn skip cho phép việc skip Loading và Block UI, cùng xem đoạn code sau nhé
let counter = 0;
// Handle each reuqest send
$(document)
.ajaxSend(function (event, xhr, options) {
counter++;
if (getUrlParam("noBlockUI", options.url) === "true") {
console.log("None Block UI: " + options.url); // https://jsonplaceholder.typicode.com/posts?noBlockUI=true
return;
}
//set loading and block ui
if (counter > 0) {
$("#loading").show();
$("#overlay").show();
}
})
.ajaxComplete(function (event, xhr, options) {
counter--;
// unset loading and block ui
if (counter <= 0) {
$("#loading").hide();
$("#overlay").hide();
}
});
let getUrlParam = function (name, url) {
let results = new RegExp("[?&]" + name + "=([^&#]*)").exec(url);
if (results == null) {
return null;
}
return decodeURI(results[1]) || 0;
};
let getListNoneBlock = function () {
$.ajax({
cache: false,
type: "GET",
url: "https://jsonplaceholder.typicode.com/posts?noBlockUI=true",
success: function (data) {
console.log(data);
},
error: function (data) {},
});
};
// Main function
$(function () {
getListNoneBlock();
});
Tại mỗi URL các bạn thêm param noBlockUI=true hoặc bất cứ giá trị nào mà bạn muốn để check (không cần thêm Param này ở Back End nhé)
Kết luận
Vậy là chúng ta đã giải quết xong việc tự động hanlde và xử lý các logic trước và sau khi gọi Ajax trong việc show/hide Loading và Block UI bằng ajaxSend và ajaxComplete. Điều này thật sự rất hữu ích cho các bạn làm web và làm việc với Ajax để call request. Hi vọng bài viết này có thể giúp ích cho các bạn. Nếu cần source code thì hãy Download ở bên dưới nhé. Cám ơn đã theo dõi và hẹn gặp lại ở các blog tiếp theo nhé ~.~ Have a nice day ❤