Tìm kiếm


    Kỹ thuật Ajax Loading và UI Blocking


    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 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()$("#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 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 ❤


    Tiểu sử
    Are you one or zero?


    Bình luận