• Thứ Tư, 25/07/2007 16:00 (GMT+7)

    Lập trình Ajax Suggestion

    Từ khi xuất hiện vào đầu năm 2005 trên một bài báo của Jesse James Garette, Ajax đã ngày càng trở nên phổ biến và được ứng dụng rộng rãi, điển hình là các dịch vụ như Google Maps, Google Suggest, Gmail, Yahoo! Mail beta... Đặc biệt là công cụ Google Suggest gợi ý trước cho bạn một số từ khoá liên quan ngay khi bạn mới chỉ gõ một phần của từ khoá và số lượng kết quả tìm kiếm có được nếu bạn thực hiện tìm kiếm với từ gợi ý được chọn. (Hình)

    Cách gợi ý như của Google Suggest có thể áp dụng vào rất nhiều ứng dụng khác nhau như từ điển, tìm kiếm nhanh trên một danh sách dữ liệu... Và để áp dụng tính năng gợi ý đó vào trang web của bạn cũng không quá khó. Bài viết này hướng dẫn cách xây dựng tính năng như vậy cho một trang web, ngôn ngữ lập trình sử dụng là Java (đòi hỏi bạn phải biết lập trình web với JSP, Servlet, biết sử dụng web server Tomcat, lập trình Javascript, HTML, và một chút CSS).

    Trang web chỉ có một trang duy nhất và có chức năng tìm kiếm tên các nước trên thế giới. Khi bạn gõ một phần tên của nước cần tìm vào ô tìm kiếm, Ajax sẽ hiển thị danh sách tên các nước có bắt đầu bằng cụm từ mà bạn đang gõ, cùng một số thông tin liên quan như thủ đô, dân số, diện tích và mã điện thoại quốc tế. Bạn có thể dùng bàn phím hoặc chuột để chọn một nước trong danh sách này.

    Để cho đơn giản, dữ liệu về các nước trên thế giới được lưu trong một file text (trong thực tế có thể là một hoặc nhiều bảng trong cơ sở dữ liệu). File này gồm có nhiều dòng, mỗi dòng là thông tin về một nước, cấu trúc của một dòng như sau:

    Tên nước-Thủ đô-Dân số-Diện tích-Mã điện thoại

    Các thông tin trên một dòng được ngăn cách bằng dấu gạch nối (-). Ví dụ:

    Argentina-Kabul-21.92-647,497-54

    Australia-Canbera-19.10-7,686,848-61

    Austria-Vienna-8.10-83,853-43

    Vietnam-Hanoi-84.51-329,556-84

    Kiến trúc hoạt động của ứng dụng này xây dựng trên mô hình ba lớp, được thể hiện như sau:


    Trong đó ở tầng nghiệp vụ (Business tier) ta dùng web server là Tomcat để triển khai ứng dụng web viết bằng Java, gồm có Servlet để phân tích và xử lý yêu cầu từ trình duyệt (client); Business object thực hiện việc tìm dữ liệu theo yêu cầu của client và chuyển kết quả tìm được thành JavaBean. Tầng cơ sở dữ liệu (Database tier) có thể dùng các hệ quản trị CSDL phổ biến như MySQL, MS SQL, ... Tuy nhiên để cho đơn giản, trong ứng dụng này ta chỉ dùng một text file để lưu dữ liệu.

    Sơ đồ các thao tác thực hiện ở phía server như sau:


    Sơ đồ xử lý ở phía client như sau:


    Như đã nói ở trên, để đơn giản ta không dùng hệ quản trị CSDL nào mà chỉ dùng một file text để lưu thông tin về các nước trên thế giới. Bạn tạo một text file có cấu trúc như mô tả ở trên và đặt tên là countries.txt chẳng hạn.

    Chúng ta tạo ra một JavaBean để chứa đựng thông tin về các quốc gia, bao gồm các thuộc tính sau (tất cả các thuộc tính đều có hàm setXxx() và getXxx(), trong đó Xxx là tên thuộc tính):

     

    STT

     

     

    Tên thuộc tính

     

     

    Kiểu dữ liệu

     

     

    Mô tả

     
     

    1

     

     

    area

     

     

    String

     

     

    Diện tích

     
     

    2

     

     

    capital

     

     

    String

     

     

    Thủ đô

     
     

    3

     

     

    code

     

     

    String

     

     

    Mã điện thoại

     
     

    4

     

     

    name

     

     

    String

     

     

    Tên nước

     
     

    5

     

     

    population

     

     

    String

     

     

    Dân số

     


    Lớp này ta đặt tên là CountryBean.java, mã nguồn rất đơn giản:

    package com.hainasoft.ajax.suggestion;

    public class CountryBean {

    private String name;

    private String capital;

    private String population;

    private String area;

    private String code;

    public CountryBean(String name, String capital, String pop, String area, String code) {

    this.name = name;

    this.capital = capital;

    this.population = pop;

    this.area = area;

    this.code = code;

    }

    public String getName() {

    return name;

    }

    public void setName(String name) {

    this.name = name;

    }

    public String getCapital() {

    return capital;

    }

    public String getArea() {

    return area;

    }

    public void setCapital(String capital) {

    this.capital = capital;

    }

    public void setArea(String area) {

    this.area = area;

    }

    public String getPopulation() {

    return population;

    }

    public String getCode() {

    return code;

    }

    public void setPopulation(String population) {

    this.population = population;

    }

    public void setCode(String code) {

    this.code = code;

    }

    }

    Bạn để ý trong mã nguồn trên thì gói ứng dụng của chúng ta là com.hainasoft.ajax.suggestion.

    Tiếp theo chúng ta cần có một lớp để thực hiện thao tác nghiệp vụ, ở ứng dụng này là đọc dữ liệu từ file text và ánh xạ (mapping) các thuộc tính trong file text vào JavaBean. Chúng ta đọc qua từng dòng trong file. Với mỗi dòng đọc được, ta tách các thuộc tính trong chuỗi bằng ký tự ngăn cách là dấu gạch nối (-), kết quả được một mảng String với phần tử thứ 0 là tên nước, phần tử thứ 1 là thủ đô, ... Sau đó chúng ta tạo một object của lớp CountryBean để ánh xạ các thuộc tính này vào thông qua constructor của lớp, và mỗi object chứa thông tin của một nước này lại được thêm vào một List là kết quả trả về. Tất cả đặt trong một phương thức duy nhất là getCountries() của lớp CountryManager.java, mã nguồn của lớp này cũng rất đơn giản:

    package com.hainasoft.ajax.suggestion;

    import java.io.*;

    import java.util.*;

    public class CountryManager {

    private static final String countryFile = "countries.txt";

    public List getCountries() throws FileNotFoundException, IOException {

    List result = new ArrayList();

    File f = new File(countryFile);

    FileInputStream fis = new FileInputStream(f);

    BufferedReader reader = new BufferedReader(new InputStreamReader(fis));

    String strLine = null;

    while((strLine = reader.readLine()) != null) {

    String[] lineData = strLine.split("-");

    result.add(new CountryBean(lineData[0], lineData[1], lineData[2], lineData[3], lineData[4]));

    }

    reader.close();

    return result;

    }

    }

    Tiếp theo chúng ta xây dựng một Servlet để nhận yêu cầu từ client, phân tích yêu cầu này và trả kết quả tương ứng về cho client, đặt tên cho servlet này là AjaxServlet.java. Mọi thứ chúng ta sẽ làm trong phương thức doGet() của servlet, còn phương thức doPost() thì gọi lại doGet(). Chúng ta xem qua mã nguồn trước:

    package com.hainasoft.ajax.suggestion;

    import javax.servlet.*;

    import javax.servlet.http.*;

    import java.io.*;

    import java.util.*;

    public class AjaxServlet extends HttpServlet {

    private static final String CONTENT_TYPE = "text/xml; charset=utf-8";

    //Initialize global variables

    public void init() throws ServletException {

    }

    //Process the HTTP Get request

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    /* Get keyword from query string */

    String keyword = request.getParameter("keyword");

    if(keyword == null) keyword = "";

    /* Set content type for response document */

    response.setContentType(CONTENT_TYPE);

    /* Set cache control on browser is no cache */

    response.setHeader("Cache-Control", "no-cache");

    /* Get writer from HttpServletResponse object */

    PrintWriter out = response.getWriter();

    /* XML document string response to client */

    String xmlText = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";

    /* Retrieve data from database or other data sources

    In this sample application, we read a list of country name and code from

    a text file which is stored on server

    */

    List countries = new CountryManager().getCountries();

    if(countries != null && countries.size() > 0) {

    xmlText += "<countrieslist>";

    for (Iterator it = countries.iterator(); it.hasNext(); ) {

    CountryBean country = (CountryBean) it.next();

    if(country.getName().startsWith(keyword)) {

    xmlText += "<country>";

    xmlText += "<name>" + country.getName() + "</name>";

    xmlText += "<capital>" + country.getCapital() + "</capital>";

    xmlText += "<pop>" + country.getPopulation() + "</pop>";

    xmlText += "<area>" + country.getArea() + "</area>";

    xmlText += "<code>" + country.getCode() + "</code>";

    xmlText += "</country>";

    }

    }

    xmlText += "</countrieslist>";

    } else {

    response.setStatus(HttpServletResponse.SC_NO_CONTENT);

    }

    out.write(xmlText);

    out.flush();

    }

    //Process the HTTP Post request

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    doGet(request, response);

    }

    //Clean up resources

    public void destroy() {

    }

    }

    Khi người dùng gõ vào từ khóa trong ô tìm kiếm thì chuỗi gõ vào sẽ được gửi tới servlet này, ví dụ:

    AjaxServlet?keyword=vietnam

    Servlet sẽ lấy ra giá trị của từ khóa gửi lên từ trình duyệt bằng lệnh:

    String keyword = request.getParameter("keyword");

    Để xác định kiểu của nội dung trả về cho trình duyệt, lệnh:

    response.setContentType(CONTENT_TYPE);

    sẽ đặt content type trả về cho trình duyệt là giá trị được lưu trong hằng CONTENT_TYPE, hằng này được định nghĩa như sau:

    private static final String CONTENT_TYPE = "text/xml; charset=utf-8";

    với câu lệnh trên thì kết quả trả về sẽ được trình duyệt hiểu là tài liệu text hoặc XML và ở dạng mã unicode UTF-8.

    Để trình duyệt không lưu cache của kết quả trả về khi tương tác giữa trình duyệt và servlet, ta dùng câu lệnh:

    response.setHeader("Cache-Control", "no-cache");

    Đây cũng là một câu lệnh rất phổ biến khi viết Servlet trong các ứng dụng Ajax.

    Nội dung trả về là một chuỗi thể hiện nội dung của file XML, được lưu trong biến xmlText. Cấu trúc của file XML trả về cho trình duyệt có dạng như sau:

    <?xml version="1.0" encoding="utf-8"?>

    <countrieslist>

    <country>

    <name>...</name>

    <capital>...</capital>

    <pop>...</pop>

    <area>...</area>

    <code>...</code>

    </country>

    <country>

    <name>...</name>

    <capital>...</capital>

    <pop>...</pop>

    <area>...</area>

    <code>...</code>

    </country>
    ...

    </countrieslist>

    Tiếp theo ta tạo ra một đối tượng của lớp CountryManager, gọi phương thức getCountries() của lớp này để lấy về danh sách tất cả các nước có trong file countries.txt:

    List countries = new CountryManager().getCountries();

    Kiểm tra xem danh sách kết quả trả về không được null và phải có chứa phần tử:

    if(countries != null && countries.size() > 0)

    nếu không, thì đặt trạng thái cho thông báo HTTP sẽ gửi về cho trình duyệt là không có nội dung bằng hằng SC_NO_CONTENT của lớp HttpServletResponse:

    response.setStatus(HttpServletResponse.SC_NO_CONTENT);

    Nếu danh sách kết quả trả về chứa phần tử thì duyệt qua lần lượt từng phần tử trong danh sách này, ép kiểu về đối tượng CountryBean và kiểm tra xem thuộc tính name có bắt đầu bằng từ khóa người dùng vừa gõ vào hay không, nếu có thì tạo ra một element XML có tên là country với các element con là các thuộc tính của nước nó được lấy ra bằng các phương thức get của JavaBean.

    if(country.getName().startsWith(keyword)) {

    xmlText += "<country>";

    xmlText += "<name>" + country.getName() + "</name>";

    xmlText += "<capital>" + country.getCapital() + "</capital>";

    xmlText += "<pop>" + country.getPopulation() + "</pop>";

    xmlText += "<area>" + country.getArea() + "</area>";

    xmlText += "<code>" + country.getCode() + "</code>";

    xmlText += "</country>";

    }

    Cuối cùng ghi kết quả về cho client thông qua phương thức write của đối tượng PrintWriter:

    out.write(xmlText);

    out.flush();

    Ở phía client chúng ta có một file HTML để hiển thị giao diện và một thư viện Javascript để xử lý tương tác giữa client và server. File HTML chúng ta đặt tên là index.html, còn file Javascript tên là ajaxsuggest.js.

    File index.html tạo ra một text box để người dùng gõ từ khóa vào:

    <input type="text" name="country" id="country" autocomplete="off" onKeyUp="doSuggestion(event);"/>

    Sự kiện onKeyUp của text box sẽ gọi hàm doSuggestion() trong file ajaxsuggest.js (xem giải thích cụ thể trong mã nguồn). Một thẻ <div> để hiển thị kết quả tìm kiếm:

    <div id="divCountry" style="position:relative; visibility:hidden; z-index:1"></div>

    Chúng ta sẽ dùng Javascript để cập nhật mã HTML của thẻ <div> này mỗi khi có kết quả trả về từ server.

    Nội dung đầy đủ của file index.html:

    <html>

    <head>

    <title>AJAX Suggestion Sample</title>

    </head>

    <script language="javascript" src="ajaxsuggest.js"></script>

    <body>

    <table border="0" width="100%">

    <form method="POST">

    <tr>

    <td align="right">

    Type part of your country name:

    </td>

    <td align="left">

    <input type="text" name="country" id="country" autocomplete="off" onKeyUp="doSuggestion(event);"/>

    <input type="hidden" name="zipCode" id="zipCode"/>

    </td>

    </tr>

    <tr>

    <td></td>

    <td align="left">

    <div id="divCountry" style="position:relative; visibility:hidden; z-index:1"></div>

    </td>

    </tr>

    </form>

    </table>

    </body>

    <script language="javascript">

    setupSuggestion(

    "ajaxservlet?keyword=",

    "country",

    "zipCode",

    "divCountry",

    "#000000",

    "#FFFFFF",

    "#99CCFF"

    );

    </script>

    </html>

    File cấu hình cho ứng dụng web là web.xml ta chỉ cần cấu hình URL của servlet giống với tham số truyền vào trong lời gọi hàm setupSuggestion() của file index.html, nội dung như sau:

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

    <web-app>

    <display-name>AjaxSuggestion</display-name>

    <servlet>

    <servlet-name>Suggestion Ajax Servlet</servlet-name>

    <servlet-class>com.hainasoft.ajax.suggestion.AjaxServlet</servlet-class>

    </servlet>

    <servlet-mapping>

    <servlet-name>Suggestion Ajax Servlet</servlet-name>

    <url-pattern>/ajaxservlet</url-pattern>

    </servlet-mapping>

    <session-config>

    <session-timeout>10</session-timeout>

    </session-config>

    </web-app>

    Như vậy phần code cho ứng dụng đã xong, bạn biên dịch các file .java và đóng gói thành file WAR cùng với các file index.html, ajaxsuggest.js và web.xml. Triển khai ứng dụng trên web server là Tomcat và chạy thử. File countries.txt bạn đặt tại thư mục cài web server hoặc nếu bạn đặt ở thư mục khác thì phải chỉ ra đường dẫn trong hằng countryFile của lớp CountryManager.

    Gõ địa chỉ vào trình duyệt, ví dụ: http://localhost/AjaxSuggestion/index.html. Nhập vào ô tìm kiếm chữ "a" thì ngay lập tức tên các nước bắt đầu bằng chữ "a" hiện lên, dùng phím mũi tên lên, xuống hoặc chuột để tô sáng tên nước cần chọn trong danh sách. Muốn chọn thì bấm Enter hoặc nhấn chuột trái. Nếu bạn càng gõ thêm nhiều chữ cái nữa thì danh sách kết quả sẽ càng thu hẹp dần.

    Bạn có thể liên hệ với tạp chí Thế Giới Vi Tính hoặc tác giả bài viết qua địa chỉ email để có mã nguồn đầy đủ (JBuiler project) của ứng dụng này.

    Hà Minh Nam
    Email: ha_minh_nam@hotmail.com

    ID: A0707_138
    File đính kèm