III. Sử dụng XML
Trong phần đầu tiên của topic này, chúng ta đã tìm hiểu cách sử dụng iframe để load trước dữ liệu và cập nhật nội dung cho 1 trang web mà không cần tải lại nó. Bây giờ chúng ta sẽ xem xét 1 phương pháp khác tinh tế hơn, đó là XML.
Chỉ tính riêng RSS và ASP .Net cũng đủ thấy những giá trị ứng dụng không nhỏ mà XML có thể đem lại cho chúng ta. Lần đầu tiên viết được 1 hàm xử lý XML, tôi cảm thấy thật sự hứng khởi. Cả thế giới vẫn không ngớt ca tụng XML như 1 công nghệ của tương lai. Và khi tôi biết được một chút gì đó về XML, điều đó có nghĩa giống như tôi đã nắm bắt được 1 phần tương lai của thế giới. Vĩ đại thay !
Nói đến XML là nói đến cấu trúc dữ liệu. Việc đầu tiên mà bất cứ nhà phát triển web nào cũng phải làm là hình dung dữ liệu sẽ được XML mô tả như thế nào. Mặt khác, chính cách tổ chức dữ liệu của bạn sẽ quyết định phương thức mà bạn phải làm để xử lý dữ liệu đó. Ở đây, thêm một lần nữa, chúng ta thấy rõ mối quan hệ chặt chẽ giữa cấu trúc dữ liệu và giải thuật.
Điều may mắn là XML khá trực quan. Chỉ cần bạn tưởng tượng một cách rõ ràng kết quả cuối cùng mà bạn muốn nhận được, thì bạn sẽ biết chắc bạn phải làm gì.
Trở lại với ví dụ ở phần đầu tiên, chúng ta có 1 bảng tblNews, bây giờ chúng ta sẽ thêm vào 1 trường là Title để thể hiện tiêu đề của 1 mục tin. Bảng sẽ có dạng sau :

Mong muốn của chúng ta là liệt kê 1 danh sách tiêu đề ở phần bên trái, sao cho khi nhấn lên mỗi tiêu đề thì nội dung tương ứng sẽ được load vào phần bên phải. Như hình ảnh này :

Cái mà chúng ta cần để trình bày là tiêu đề và nội dung của mỗi mục tin. Do vậy, cấu trúc XML của chúng ta có thể như dưới đây :

Bao giờ hồ sơ XML cũng yêu cầu 1 nút gốc. Bạn đặt tên gì cũng được. Còn ở đây tôi chọn nút gốc là SNNews. Dưới SNNews có 2 nút con title và story chứa tiêu đề và nội dung. Không cần giản đồ hay DTD gì cho mất thời gian, hãy đơn giản như bản chất của XML !
Giá trị của trường NewsID sẽ giúp chúng ta truy vấn mục tin trong cơ sở dữ liệu. 1 trang xml.php sẽ giúp tạo ra nội dung XML này. 2 câu lệnh header đầu tiên ngăn cản việc cache trang và quy định dữ liệu sẽ trả về theo khuôn dạng XML :
Code không phức tạp, tôi chỉ xin lưu ý 2 điểm :
1. dòng mở đầu cho hồ sơ XML luôn có dạng :
Riêng dòng này trong PHP, bạn phải dùng chính PHP viết ra để tránh làm cho trình biên dịch hiểu nhầm đây là 1 đoạn PHP script, do cấu trúc thẻ có dạng <?...?>.
2. XML có 1 số ký tự riêng, mà nếu muốn dùng đến như 1 phần giá trị của node, bạn phải đưa về dạng thực thể ký tự. Do đó chúng ta dùng hàm htmlspecialchars.
Bạn cần kiểm tra lại sao cho ngay cả trong trường hợp không có mẩu tin nào truy xuất được, XML của bạn cũng không bị lỗi thì mới đảm bảo tính chính xác. Như dưới đây tôi cung cấp 1 ID không có trong database, việc gọi trang xml.php?id=0 sẽ vẫn trả về 1 file xml hợp lệ :

Bây giờ, chúng ta đi xuống phía khách. Kịch bản Java Script như sau :
Hàm loadNews nhận 1 tham số id, nó sẽ sử dụng tham số này để hoàn thiện url gọi đến file xml, bằng câu lệnh :
Chúng ta cũng khởi tạo 1 biến data với giá trị null;
Theo đặc tả của W3C về DOM, đối tượng document có 1 thuộc tính implementation trả về 1 đối tượng DOMImplementation được trình duyệt sử dụng. Mozilla và Opera đều tuân thủ chuẩn này, nhưng IE trên Window thì không.

Phương thức createDocument của DOMImplementation, cho phép tạo ra 1 thể hiện của lớp này. Các tham số của phương thức createDocument được giới thiệu khá rõ trên W3Schools :
http://www.w3schools.com/dom/dom_parser.asp
Do đó, đầu tiên chúng ta kiểm tra trình duyệt có tuân thủ đúng chuẩn hay không, nếu có, chúng ta khởi tạo 1 instance của đối tượng xử lý DOMDocument và gán vào biến data. Phương thức load sẽ tải xuống hồ sơ XML theo đường dẫn chỉ định trong tham số của nó.
Khi load thành công, sự kiện onload trên data sẽ gọi hàm parseXML để duyệt cây hồ sơ XML. Nhưng hàm parseXML cần 1 tham số và chúng ta không thể viết :
Vì vậy, tôi dùng 1 hàm động và gọi parseXML từ bên trong nó :
Với IE, Microsoft đưa vào phiên bản 5.0 đối tượng ActiveX, có tên Microsoft.XMLDOM để làm nhiệm vụ xử lý dữ liệu XML. Phần kế tiếp của loadNews sẽ kiểm tra trình duyệt có ActiveX hay không, nếu có, chúng ta khởi tạo 1 thể hiện của Microsoft.XMLDOM và gán vào data.
Thiết lập data.async=true chỉ định việc tải xuống hồ sơ XML đồng bộ. Nếu bạn đặt là false, có nghĩa là không cho phép điều này, và kịch bản sẽ dừng lại sau câu lệnh để chờ cho đến khi hồ sơ XML được tải xuống trọn vẹn rồi mới thực thi tiếp, giống như khi đang có một alert bật ra vậy.
data.load(url) cũng tương tự như trên. Phương thức này tải hồ sơ XML từ 1 url chỉ định.
Khi bạn thiết lập thuộc tính async là true, script sẽ tiếp tục thực thi song song với quá trình tải hồ sơ. Như vậy, chúng ta cần 1 cái gì đó để nắm bắt tiến trình load này và nhận biết khi nào trình duyệt tải xong. Script không thể phân tích 1 hồ sơ chưa đầy đủ. Vì vậy :
Sự kiện onreadystatechange được kích hoạt khi thuộc tính readyState thay đổi giá trị. Một hàm động sẽ có nhiệm vụ theo dõi tiến trình cho đến khi readyState đạt tới giá trị 4, nghĩa là khi hồ sơ đã tải xuống hoàn chỉnh. Lúc đó, chúng ta mới gọi parseXML.
Hàm parseXML có nhiệm vụ phân tích dữ liệu trong DOMDocument Object, nó nhận vào 1 tham số là thể hiện của DOMImplementation hoặc Microsoft.XMLDOM. Với phương thức getElementsByTagName, chúng ta lấy phần tử gốc của cây hồ sơ XML và gán vào biến root. Ở đây, phần tử gốc theo cấu trúc của chúng ta định nghĩa là phần tử SNNews.
Hồ sơ chỉ có 1 phần tử SNNews duy nhất, chúng ta tham chiếu đến nó bằng chỉ mục là 0. Tiếp tục với getElementsByTagName, chúng ta lấy được các phần tử trực hệ của SNNews bằng cách gọi :
Tuy nhiên, nhằm truy xuất giá trị lưu trong các phần tử title và story để sử dụng, chúng ta code :
Bây giờ biến title sẽ chứa tiêu đề của mục tin, và story mang nội dung mục tin. Các câu lệnh cuối chỉ định script cho xuất hiện các giá trị này ra trình duyệt.
Tóm lại, mỗi khi bạn gọi loadNews(id), với id là giá trị trong trường ID của mục tin lưu trữ tại bảng tblNews, JavaScript sẽ gọi file xml.php với tham số GET cụ thể. Phía server, kịch bản PHP nhận được tham số GET, truy vấn cơ sở dữ liệu và trả về nội dung mục tin ở dạng XML để gửi trở lại trình duyệt khách. Java Script đón lấy hồ sơ XML này, dùng các thao tác của đối tượng DOMDocument và phân tích, xử lý để cuối cùng trình bày ra cho người sử dụng thấy nội dung mới. Đó là toàn bộ quy trình hoạt động của ứng dụng mà chúng ta vừa xây dựng.
Demo :
http://lovepm.uni.cc/Examples/AJAX/XMLData/
Trong phần đầu tiên của topic này, chúng ta đã tìm hiểu cách sử dụng iframe để load trước dữ liệu và cập nhật nội dung cho 1 trang web mà không cần tải lại nó. Bây giờ chúng ta sẽ xem xét 1 phương pháp khác tinh tế hơn, đó là XML.
Chỉ tính riêng RSS và ASP .Net cũng đủ thấy những giá trị ứng dụng không nhỏ mà XML có thể đem lại cho chúng ta. Lần đầu tiên viết được 1 hàm xử lý XML, tôi cảm thấy thật sự hứng khởi. Cả thế giới vẫn không ngớt ca tụng XML như 1 công nghệ của tương lai. Và khi tôi biết được một chút gì đó về XML, điều đó có nghĩa giống như tôi đã nắm bắt được 1 phần tương lai của thế giới. Vĩ đại thay !
Nói đến XML là nói đến cấu trúc dữ liệu. Việc đầu tiên mà bất cứ nhà phát triển web nào cũng phải làm là hình dung dữ liệu sẽ được XML mô tả như thế nào. Mặt khác, chính cách tổ chức dữ liệu của bạn sẽ quyết định phương thức mà bạn phải làm để xử lý dữ liệu đó. Ở đây, thêm một lần nữa, chúng ta thấy rõ mối quan hệ chặt chẽ giữa cấu trúc dữ liệu và giải thuật.
Điều may mắn là XML khá trực quan. Chỉ cần bạn tưởng tượng một cách rõ ràng kết quả cuối cùng mà bạn muốn nhận được, thì bạn sẽ biết chắc bạn phải làm gì.
Trở lại với ví dụ ở phần đầu tiên, chúng ta có 1 bảng tblNews, bây giờ chúng ta sẽ thêm vào 1 trường là Title để thể hiện tiêu đề của 1 mục tin. Bảng sẽ có dạng sau :

Mong muốn của chúng ta là liệt kê 1 danh sách tiêu đề ở phần bên trái, sao cho khi nhấn lên mỗi tiêu đề thì nội dung tương ứng sẽ được load vào phần bên phải. Như hình ảnh này :

Cái mà chúng ta cần để trình bày là tiêu đề và nội dung của mỗi mục tin. Do vậy, cấu trúc XML của chúng ta có thể như dưới đây :

Bao giờ hồ sơ XML cũng yêu cầu 1 nút gốc. Bạn đặt tên gì cũng được. Còn ở đây tôi chọn nút gốc là SNNews. Dưới SNNews có 2 nút con title và story chứa tiêu đề và nội dung. Không cần giản đồ hay DTD gì cho mất thời gian, hãy đơn giản như bản chất của XML !
Giá trị của trường NewsID sẽ giúp chúng ta truy vấn mục tin trong cơ sở dữ liệu. 1 trang xml.php sẽ giúp tạo ra nội dung XML này. 2 câu lệnh header đầu tiên ngăn cản việc cache trang và quy định dữ liệu sẽ trả về theo khuôn dạng XML :
<? header("Cache-Control: no-cache, must-revalidate"); header("Content-type: text/xml"); // Tạo 2 giá trị mặc định cho 2 node Title và Story. $title="No data here"; $story=""; $id=trim($_GET['id']); $sql="Select * FROM tblNews WHERE NewsID='".addslashes($id)."'"; // kết nối cơ sở dữ liệu MySQL từ localhostt, với username là root, password rỗng : @$conn = mysql_connect("localhost","root",""); if(!$conn){ die('Could not connect database in this time ! '); exit(); } // Mở cơ sở dữ liệu có tên News để làm việc mysql_select_db("News", $conn); // thực hiện truy vấn SQL, trả kết quả vào biến $result : $result=mysql_query($sql); // Nếu có mẩu tin nào đó : if(mysql_num_rows($result)>0){ // xem xét nội dung truy vấn ở dạng array : $row=mysql_fetch_array($result); // Gán lại nội dung của các phần tử mảng $row cho $title, và $story : $title=$row['Title']; $story=$row['Story']; } mysql_free_result($result); mysql_close($conn); // trình bày theo đúng định dạng XML echo'<?xml version="1.0" encoding="UTF-8"?>'; ?> <SNNews> <title><?=htmlspecialchars($title)?></title> <story><?=htmlspecialchars($story)?></story> </SNNews>
Code không phức tạp, tôi chỉ xin lưu ý 2 điểm :
1. dòng mở đầu cho hồ sơ XML luôn có dạng :
<?xml version="1.0"?>
Riêng dòng này trong PHP, bạn phải dùng chính PHP viết ra để tránh làm cho trình biên dịch hiểu nhầm đây là 1 đoạn PHP script, do cấu trúc thẻ có dạng <?...?>.
2. XML có 1 số ký tự riêng, mà nếu muốn dùng đến như 1 phần giá trị của node, bạn phải đưa về dạng thực thể ký tự. Do đó chúng ta dùng hàm htmlspecialchars.
Bạn cần kiểm tra lại sao cho ngay cả trong trường hợp không có mẩu tin nào truy xuất được, XML của bạn cũng không bị lỗi thì mới đảm bảo tính chính xác. Như dưới đây tôi cung cấp 1 ID không có trong database, việc gọi trang xml.php?id=0 sẽ vẫn trả về 1 file xml hợp lệ :

Bây giờ, chúng ta đi xuống phía khách. Kịch bản Java Script như sau :
<script type="text/javascript"> function rel(ob){return document.getElementById(ob);} function gTxt(ob,txt){rel(ob).innerHTML=txt;} function loadNews(id){ var data=null, url="xml.php?id="+id; if(document.implementation&&document.implementation.createDocument){ data=document.implementation.createDocument("","",null); data.load(url); data.onload=function (){parseXML(data);} } else if(window.ActiveXObject){ data=new ActiveXObject("Microsoft.XMLDOM"); data.async=true; data.load(url); data.onreadystatechange=function (){ if(data.readyState==4){ parseXML(data); } } } else{ alert("Sorry, your web browser is not yet supported."); return; } } function parseXML(data){ var root=data.getElementsByTagName("SNNews"); var title=root[0].getElementsByTagName("title")[0].firstChild.nodeValue; var story=root[0].getElementsByTagName("story")[0].firstChild.nodeValue; var s="<h4>"+title+"</h4>"+story; gTxt('news',s); } </script>
Hàm loadNews nhận 1 tham số id, nó sẽ sử dụng tham số này để hoàn thiện url gọi đến file xml, bằng câu lệnh :
url="xml.php?id="+id;
Chúng ta cũng khởi tạo 1 biến data với giá trị null;
Theo đặc tả của W3C về DOM, đối tượng document có 1 thuộc tính implementation trả về 1 đối tượng DOMImplementation được trình duyệt sử dụng. Mozilla và Opera đều tuân thủ chuẩn này, nhưng IE trên Window thì không.

Phương thức createDocument của DOMImplementation, cho phép tạo ra 1 thể hiện của lớp này. Các tham số của phương thức createDocument được giới thiệu khá rõ trên W3Schools :
http://www.w3schools.com/dom/dom_parser.asp
Do đó, đầu tiên chúng ta kiểm tra trình duyệt có tuân thủ đúng chuẩn hay không, nếu có, chúng ta khởi tạo 1 instance của đối tượng xử lý DOMDocument và gán vào biến data. Phương thức load sẽ tải xuống hồ sơ XML theo đường dẫn chỉ định trong tham số của nó.
Khi load thành công, sự kiện onload trên data sẽ gọi hàm parseXML để duyệt cây hồ sơ XML. Nhưng hàm parseXML cần 1 tham số và chúng ta không thể viết :
data.onload=parseXML(data); // sai quy tắc
Vì vậy, tôi dùng 1 hàm động và gọi parseXML từ bên trong nó :
data.onload=function (){ parseXML(data); }
Với IE, Microsoft đưa vào phiên bản 5.0 đối tượng ActiveX, có tên Microsoft.XMLDOM để làm nhiệm vụ xử lý dữ liệu XML. Phần kế tiếp của loadNews sẽ kiểm tra trình duyệt có ActiveX hay không, nếu có, chúng ta khởi tạo 1 thể hiện của Microsoft.XMLDOM và gán vào data.
else if(window.ActiveXObject){ data=new ActiveXObject("Microsoft.XMLDOM");
Thiết lập data.async=true chỉ định việc tải xuống hồ sơ XML đồng bộ. Nếu bạn đặt là false, có nghĩa là không cho phép điều này, và kịch bản sẽ dừng lại sau câu lệnh để chờ cho đến khi hồ sơ XML được tải xuống trọn vẹn rồi mới thực thi tiếp, giống như khi đang có một alert bật ra vậy.
data.load(url) cũng tương tự như trên. Phương thức này tải hồ sơ XML từ 1 url chỉ định.
Khi bạn thiết lập thuộc tính async là true, script sẽ tiếp tục thực thi song song với quá trình tải hồ sơ. Như vậy, chúng ta cần 1 cái gì đó để nắm bắt tiến trình load này và nhận biết khi nào trình duyệt tải xong. Script không thể phân tích 1 hồ sơ chưa đầy đủ. Vì vậy :
data.onreadystatechange=function (){ if(data.readyState==4){ parseXML(data); } }
Sự kiện onreadystatechange được kích hoạt khi thuộc tính readyState thay đổi giá trị. Một hàm động sẽ có nhiệm vụ theo dõi tiến trình cho đến khi readyState đạt tới giá trị 4, nghĩa là khi hồ sơ đã tải xuống hoàn chỉnh. Lúc đó, chúng ta mới gọi parseXML.
Hàm parseXML có nhiệm vụ phân tích dữ liệu trong DOMDocument Object, nó nhận vào 1 tham số là thể hiện của DOMImplementation hoặc Microsoft.XMLDOM. Với phương thức getElementsByTagName, chúng ta lấy phần tử gốc của cây hồ sơ XML và gán vào biến root. Ở đây, phần tử gốc theo cấu trúc của chúng ta định nghĩa là phần tử SNNews.
Hồ sơ chỉ có 1 phần tử SNNews duy nhất, chúng ta tham chiếu đến nó bằng chỉ mục là 0. Tiếp tục với getElementsByTagName, chúng ta lấy được các phần tử trực hệ của SNNews bằng cách gọi :
root[0].getElementsByTagName("title"); root[0].getElementsByTagName("story");
Tuy nhiên, nhằm truy xuất giá trị lưu trong các phần tử title và story để sử dụng, chúng ta code :
var title=root[0].getElementsByTagName("title")[0].firstChild.nodeValue; var story=root[0].getElementsByTagName("story")[0].firstChild.nodeValue;
Bây giờ biến title sẽ chứa tiêu đề của mục tin, và story mang nội dung mục tin. Các câu lệnh cuối chỉ định script cho xuất hiện các giá trị này ra trình duyệt.
Tóm lại, mỗi khi bạn gọi loadNews(id), với id là giá trị trong trường ID của mục tin lưu trữ tại bảng tblNews, JavaScript sẽ gọi file xml.php với tham số GET cụ thể. Phía server, kịch bản PHP nhận được tham số GET, truy vấn cơ sở dữ liệu và trả về nội dung mục tin ở dạng XML để gửi trở lại trình duyệt khách. Java Script đón lấy hồ sơ XML này, dùng các thao tác của đối tượng DOMDocument và phân tích, xử lý để cuối cùng trình bày ra cho người sử dụng thấy nội dung mới. Đó là toàn bộ quy trình hoạt động của ứng dụng mà chúng ta vừa xây dựng.
Demo :
http://lovepm.uni.cc/Examples/AJAX/XMLData/