Thay đổi đường dẫn URL mà không tải lại trang 23/01/2016

Thay đổi đường dẫn URL mà không tải lại trang

Trong bài viết này, iTOOL sẽ trình bày cách để thực hiện thay đổi đường dẫn URL hiển thị trên location bar của trình duyệt mà không phải tải lại trang khi thiết kế website. Điều này sẽ rất hữu dụng nếu bạn muốn xử lý nội dung trang bằng javascript nhưng lại muốn sự thay đổi đó ánh xạ lên url giúp người dùng dễ dàng bookmark hoặc chia sẻ link với người khác.

CÔNG NGHỆ TRUYỀN THỐNG
Với cách truyền thống, ta sử dụng # để thay đổi url mà không load lại trang.
Ví dụ cho trường hợp sau: ta có trang news.html liệt kê danh sách các tin tức, khi người dùng click vào tiêu đề các tin để đọc thì thay vì chuyển sang một trang mới, ta thực hiện các bước:
  • Ẩn danh sách tin
  • Thay đổi url thành news.html#id=7
  • Load bằng ajax nội dung tương ứng với title tin có id=7 (cần có một trang trên server phục vụ việc lấy nội dung tin khi biết id)
  • Dùng Javascript hiển thị nội dung tin tức
  • Ưu điểm của cách này: Hỗ trợ bởi tất cả các trình duyệt thông dụng. Nhược điểm: chỉ được thay đổi sau #, không tạo được history nếu phần sau # không đổi (ví dụ từ #foo chuyển sang #foo) và các dữ liệu phải đóng gói trong không gian chật hẹp trên url.
SỬ DỤNG HISTORY.PUSHSTATE
Các trình duyệt mới bây giờ (FF4+, Chrome 5+, Safari 5+; IE10+, Opera 11.5+) cung cấp cho ta chức năng history.pushState cho phép lưu trạng thái mong muốn vào history để tạo thành một trạng thái cho phép lấy lại được khi người dùng click nút BACK.
 
history.pushState(stateObj, title, url)

stateObj: đối tượng javascript cần lưu lại (hiện tại với Firefox thì giới hạn dung lượng là 640kB)
title: tiêu đề (hiện tại thì nó chưa có ý nghĩa gì cả)
url: đường dẫn mới của trang web (là bất kỳ miễn sao cùng domain là được)

Khi người dùng click phím BACK, ta có thể lấy lại được stateObj bằng cách cài đặt xử lý sự kiện window.onpopstate

Ưu điểm của pushState: URL mới là bất kỳ miễn sao cùng domain, luôn tạo được một entry trong window.history, có thể lưu lại một lượng lớn thông tin dùng cho việc quay lại mà hoàn toàn trong suốt với người dùng.

VÍ DỤ ĐƠN GIẢN
Tạo một trang html đơn giản như sau:
 
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Demo Simple PushState - tek.eten.vn</title>
    <style type="text/css">
        body{
            text-align: center;
            font-family: Tahoma, Arial;
        }
    </style>
    <script type="text/javascript">  
        function demo(){
            history.pushState({}, "", "htm.simple");
            document.body.innerHTML = "Giờ URL của bạn đã được chuyển sang trang htm.simple mặc dù trang này không hề tồn tại!";
        }
    </script>
</head>
<body>
    <input type="button" value="Click để chuyển URL nào" onclick="demo()" />
</body>
</html>

Xem DEMO tại đây
Ví dụ trên là khá đơn giản, nó chỉ thực hiện duy nhất một tính năng là chuyển url thành “htm.simple”

Dựa vào pushState, ta có thể xây dựng các tính năng phức tạp hơn như là xử lý phím BACK để lấy lại dữ liệu cũ, chuyển url sang trang có thật để nếu người dùng có chia sẻ với bạn bè hay lên các trang khác thì vẫn xem được nội dung, kết hợp với ajax làm giảm lưu lượng đường truyền (thay vì phải load lại toàn trang).

Sau đây tôi sẽ xây dựng 1 album ảnh nho nhỏ.
Trước tiên bạn chuẩn bị vài cái ảnh, sau đó tạo một trang trên server cho phép lấy ảnh bằng cách truyền id ảnh vào. Ví dụ tôi dùng một trang ashx có tên là Content.ashx như sau:
 
using System;
using System.Web;
public class Content : IHttpHandler {
    public void ProcessRequest (HttpContext context) {
        var imgs = new string[] {
                                "đường dẫn ảnh 1",
                                "đường dẫn ảnh 2",
                                "đường dẫn ảnh 3",
                                "đường dẫn ảnh 4"
                            };
        var template = "<a href='?id={0}'><img src='{1}' /></a>";
        context.Response.ContentType = "text/plain";
        int id;
        if (int.TryParse(context.Request.QueryString["img"], out id) && id > 0 && id < imgs.Length)
            id++;
        else
            id=1;
        context.Response.Write(string.Format(template, id, imgs[id-1]));
        context.Response.End();
    }
    public bool IsReusable {
        get {
            return false;
        }
    }
}

Đơn giản nó nhận id ảnh thông qua cách gọi Content.ashx?id={id của ảnh} và trả lại tên ảnh tương ứng với trong cái mảng.

Tiếp theo tạo một trang html, chuẩn bị một link và một nơi chứa nội dung:
 
<a id="albumLink" href="?id=1" onclick="$(this).hide(); return loadImage(1)">Xem album ảnh</a>
<div id="dcp"></div>

khi click vào link xem album, thực hiện ẩn chính nó, hàm loadImage thực hiện load ảnh khi biết id, nếu trình duyệt hỗ trợ thì trả về false để ngăn không cho chuyển sang trang "?id=1".
div có id là dcp là nơi hiển thị ảnh

Viết hàm loadImage:
 
var loading = false;
function loadImage(index) {
    if (loading) return false; // nếu đang load thì thôi
    loading = true; // báo là đang load
    $("#dcp").show(); // hiển thị div dcp ra
    url = "?id=" + index; // url sẽ hiển thị trên trình duyệt
    $.ajax({ // dùng ajax để lấy ảnh về
        url: "Content.ashx?img=" + index, // đường dẫn lấy ảnh
        success: function (data) {
            // tạo trạng thái mới
            var currentState = { html: data, title: "Image " + index };
            $("#dcp").html(currentState.html); // gán nội dung html
            document.title = currentState.title; // gán lại title cho trang
            // nếu trình duyệt hỗ trợ thì lưu lại trạng thái hiện tại và chuyển url
            if (history.pushState) history.pushState(currentState, "", url);
            loading = false; // báo là đã load xong
        }
    });
    return !history.pushState; // ngăn việc chuyển trang thực sự nếu trình duyệt có hỗ trợ pushState
}
Đoạn javascript sau giúp xử lý khi người dùng vào trang bằng link có id của ảnh:

$(function () {        
    // xử lý trường hợp request (method GET)
    if (location.search.length > 0) {
        $("#albumLink").hide(); // ẩn link xem album
        // load ảnh
        loadImage(parseInt(location.search.substr("?id=".length)));
    }
    else {
        var currentState = { html: "", title: document.title };
        if (history.pushState) history.pushState(currentState, "", url);
    }
});
Cuối cùng là xử lý khi người dùng click vào nút BACK hoặc FORWARD, sử dụng sự kiện onpopstate:

window.onpopstate = function (e) {
    if (e.state) { // dùng state để gán lại cho div dcp và title của document
        $("#dcp").html(e.state.html);
        document.title = e.state.title;
         $("#albumLink").hide();
         $("#dcp").show();
    }
    else { // đã back về trang đầu
        $("#albumLink").show();
        $("#dcp").hide();
    }
};

Để xem DEMO đầy đủ, bạn vào trang DEMO ONLINE này hoặc DOWNLOAD SOURCE
Facebook chat