홈페이지 관련/ajax2007. 1. 31. 01:49

사용자 삽입 이미지

<Google Suggestion 의 ajax 를 활용한 자동완성 기능>

위 그림과 같은 자동완성 기능은 Google Suggest 에서 가장 먼저 구현하였다. 그 후 국내에는 네이버를 필두로 지금은 거의 모든 포탈업체의 검색창에는 마치 유행처럼 위와 같은 기능이 구현되어 있다.

사용자 삽입 이미지

<네이버의 자동완성 기능>


사용자 삽입 이미지
<다음의 자동완성 기능>
 
 
필자는 2005년 4월경 ajax 대신에 iframe 을 사용하여 자동완성 기능을 구현한 적이 있는데, 검색된 결과를 보여주는 창을 하나의 파일로 따로 만들어서 사용했었고 속도도 다소 느렸던 것으로 기억한다.
 
그럼 ajax 를 이용하여 단순한 자동완성 기능 예제를 살펴보자.
 
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Ajax Auto Complete</title>
    <style type="text/css">
    .mouseOut {
    background: #708090;
    color: #FFFAFA;
    }
    .mouseOver {
    background: #FFFAFA;
    color: #000000;
    }
    </style>
    <script type="text/javascript">
        var xmlHttp;
        var completeDiv;
        var inputField;
        var nameTable;
        var nameTableBody;
        function createXMLHttpRequest() {
            if (window.ActiveXObject) {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            else if (window.XMLHttpRequest) {
                xmlHttp = new XMLHttpRequest();    
            }
        }
        function initVars() {
            inputField = document.getElementById("names");
            nameTable = document.getElementById("name_table");
            completeDiv = document.getElementById("popup");
            nameTableBody = document.getElementById("name_table_body");
        }
        function findNames() {
            initVars();
            if (inputField.value.length > 0) {
                createXMLHttpRequest();
                var url = "AutoCompleteServlet?names=" + escape(inputField.value);
                xmlHttp.open("GET", url, true);
                xmlHttp.onreadystatechange = callback;
                xmlHttp.send(null);
            } else {
                clearNames();
            }
        }
        function callback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {
                    setNames(xmlHttp.responseXML.getElementsByTagName("name"));
                } else if (xmlHttp.status == 204){//데이터가 존재하지 않을 경우
                    clearNames();
                }
            }
        }
       
        function setNames(the_names) {
            clearNames();
            var size = the_names.length;
            setOffsets();
            var row, cell, txtNode;
            for (var i = 0; i < size; i++) {
                var nextNode = the_names[i].firstChild.data;
                row = document.createElement("tr");
                cell = document.createElement("td");
               
                cell.onmouseout = function() {this.className='mouseOver';};
                cell.onmouseover = function() {this.className='mouseOut';};
                cell.setAttribute("bgcolor", "#FFFAFA");
                cell.setAttribute("border", "0");
                cell.onclick = function() { populateName(this);};                    
                txtNode = document.createTextNode(nextNode);
                cell.appendChild(txtNode);
                row.appendChild(cell);
                nameTableBody.appendChild(row);
            }
        }
        function setOffsets() {
            var end = inputField.offsetWidth;
            var left = calculateOffsetLeft(inputField);
            var top = calculateOffsetTop(inputField) + inputField.offsetHeight;
            completeDiv.style.border = "black 1px solid";
            completeDiv.style.left = left + "px";
            completeDiv.style.top = top + "px";
            nameTable.style.width = end + "px";
        }
       
        function calculateOffsetLeft(field) {
          return calculateOffset(field, "offsetLeft");
        }
        function calculateOffsetTop(field) {
          return calculateOffset(field, "offsetTop");
        }
        function calculateOffset(field, attr) {
          var offset = 0;
          while(field) {
            offset += field[attr];
            field = field.offsetParent;
          }
          return offset;
        }
        function populateName(cell) {
            inputField.value = cell.firstChild.nodeValue;
            clearNames();
        }
        function clearNames() {
            var ind = nameTableBody.childNodes.length;
            for (var i = ind - 1; i >= 0 ; i--) {
                 nameTableBody.removeChild(nameTableBody.childNodes[i]);
            }
            completeDiv.style.border = "none";
        }
    </script>
  </head>
  <body>
    <h1>Ajax Auto Complete Example</h1>
    Names: <input type="text" size="20" id="names" onkeyup="findNames();" style="height:20;"/>
    <div style="position:absolute;" id="popup">
        <table id="name_table" bgcolor="#FFFAFA" border="0" cellspacing="0" cellpadding="0"/>           
            <tbody id="name_table_body"></tbody>
        </table>
    </div>
  </body>
</html>
<autoComplete.html 의 전체 소스 코드>
 
 
package ajaxbook.chap4;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.servlet.*;
import javax.servlet.http.*;
/**
 *
 * @author nate
 * @version
 */
public class AutoCompleteServlet extends HttpServlet {
    private List names = new ArrayList();
    public void init(ServletConfig config) throws ServletException {
        names.add("Abe");
        names.add("Abel");
        names.add("Abigail");
        names.add("Abner");
        names.add("Abraham");
        names.add("Marcus");
        names.add("Marcy");
        names.add("Marge");
        names.add("Marie");
    }
   
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        String prefix = request.getParameter("names");
        NameService service = NameService.getInstance(names);
        List matching = service.findNames(prefix);
        if (matching.size() > 0) {
            PrintWriter out = response.getWriter();
            response.setContentType("text/xml");
            response.setHeader("Cache-Control", "no-cache");
            out.println("<response>");
            Iterator iter = matching.iterator();
            while(iter.hasNext()) {
                String name = (String) iter.next();
                out.println("<name>" + name + "</name>");
            }
            out.println("</response>");
            matching = null;
            service = null;
            out.close();
        } else {
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
            //response.flushBuffer();
        }
    }
   
    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        doGet(request, response);
    }
       
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Short description";
    }
}
 
<AutoCompleteServlet.java 의 전체 소스 코드>
 
 
 
package ajaxbook.chap4;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
 *
 * @author nate
 */
public class NameService {
    private List names;
   
    /** Creates a new instance of NameService */
    private NameService(List list_of_names) {
        this.names = list_of_names;
    }
   
    public static NameService getInstance(List list_of_names) {
        return new NameService(list_of_names);
    }
   
    public List findNames(String prefix) {
        String prefix_upper = prefix.toUpperCase();
        List matches = new ArrayList();
        Iterator iter = names.iterator();
        while(iter.hasNext()) {
            String name = (String) iter.next();
            String name_upper_case = name.toUpperCase();
            if(name_upper_case.startsWith(prefix_upper)){       
                boolean result = matches.add(name);
            }
        }
        return matches;
    }
   
}
<NameService.java 의 전체 소스 코드>

우선 실행결과 화면을 보고 설명을 이어가겠다.

사용자 삽입 이미지

위 그림의 검색어 입력창에 a 라는 문자를 입력하면 매칭되는 문자열의 리스트를 보여주는 그림이다.

입력창에 키워드를 입력하면 이벤트가 발생하고 XHR 객체가 문자를 GET/비동기 방식으로 서버로 보낸다. 서버에서는 요청한 검색어와 매칭되는 결과를 XML 로 만들어서 클라이언트로 보내고, XHR 객체가 응답 XML 을 파싱해서 결과를 화면에 보여주는 흐름이다.

        function callback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {
                    setNames(xmlHttp.responseXML.getElementsByTagName("name"));
                } else if (xmlHttp.status == 204){//데이터가 존재하지 않을 경우
                    clearNames();
                }
            }
        }

위 메소드에는 XHR 객체의 status 코드가 204 일 경우를 처리하고 있다. 즉, 검색 결과가 없을 경우에 결과창을 지우기 위한 것이다.

위 예제에서 눈여겨 봐야 할 부분은 XML 에서 데이터를 분석해서 동적으로 결과 화면을 만드는 부분일 것이다. DOM 객체의 속성 및 메소드를 많이 접해 봐야 한다.

       function setNames(the_names) {
            clearNames();
            var size = the_names.length;
            setOffsets();

            var row, cell, txtNode;
            for (var i = 0; i < size; i++) {
                var nextNode = the_names[i].firstChild.data;
                row = document.createElement("tr");
                cell = document.createElement("td");
               
                cell.onmouseout = function() {this.className='mouseOver';};
                cell.onmouseover = function() {this.className='mouseOut';};
                cell.setAttribute("bgcolor", "#FFFAFA");
                cell.setAttribute("border", "0");
                cell.onclick = function() { populateName(this);};                    

                txtNode = document.createTextNode(nextNode);
                cell.appendChild(txtNode);
                row.appendChild(cell);
                nameTableBody.appendChild(row);
            }
        }

위 메소드는 검색결과의 한 행을 만들어 테이블의 <tr></tr> 요소를 동적으로 생성해 주는 부분이다. cell.onmouseout 및 onclick 메소드 생성할때 setAttribute() 를 사용하지 않은 이유는 IE 가 지원하지 않기 때문이다. cross-browser, 즉 모든 브라우저에서 위 샘플 실행을 보장 받기 위해서는 코딩을 위처럼 해 줘야 한다.

이것으로 4장의 강의는 모두 마친다.


-------------------------- 한글 패치 추가(2006.02.23) ---------------------

위 예제는 한글 테스트가 불가능하기 때문에 이 기회에 Ajax 의 한글문제에 대해서 생각해 보자. GET 방식이든 POST 방식이든 파라미터가 서버로 전달될때는 해당 페이지에 설정된 charset 으로 인코딩 된다. 영문이나 숫자는 문제가 안되지만 한글과 같은 유니코드 문자는 적절한 charset 으로 디코딩해주지 않으면 ??? 혹은 이상한 문자로 출력될 것이다.

Ajax 의 경우 한글 인코딩 문제는 다음과 같이 두 부분으로 나누어서 생각해 볼 수 있다.

첫번째는 브라우저의 XHR 객체에서 한글 파라미터를 인코딩하고 서버에서 디코딩해서 처리하는 경우이다. 물론 같은 charset 으로 처리 해야 된다. 이부분은 개발자가 충분히 charset을 변경할 수 있으므로 유연하게 대처할 수 있다.

두번째는 서버에서 인코딩하고 브라우저의 XHR 객체에서 한글 데이터를 디코딩하는 경우이다. 서버에서 인코딩하는 경우는 개발자가 얼마든지 바꿀 수 있지만 브라우저에서 Ajax 가 데이터를 디코딩할 경우의 charset 은 UTF-8 로 정해져 있는것 같다. 다른 charset 으로 변경할 수 있는 방법이 존재하는지는 확실하지 않지만 여러방면으로 테스트해 본 결과 UTF-8로 지정되어 있는것 같다. 따라서 서버에서 인코딩하는 부분 뿐만아니라 첫번째의 경우도 모두 charset 을 UTF-8 으로 인코딩/디코딩하는 방법이 제일 간단할 것이다.

1. 브라우저에서 charset 을 UTF-8 으로 설정하기

먼저 autoComplete.html 의 소스에서는 charset 을 UTF-8 로 인코딩 해주는 부분만 수정해주면 될 것이다.

var url = "AutoCompleteServlet?names=" + escape(inputField.value);

위 코드에서 escape 메소드는 파라미터를 유니코드방식으로 인코딩하므로 이 메소드 대신에 UTF-8 방식으로 인코딩해주는 자바스크립트 메소드인 encodeURI 혹은 encodeURIComponent 으로 아래와 같이 수정해 준다.

var url = "AutoCompleteServlet?names=" + encodeURI(inputField.value);

위 예제는 GET 방식으로 작성되었으나 POST 방식의 경우도 같은 방법으로 charset 을 설정해주면 된다. 아래 코드는 POST 방식의 findNames 메소드이다.

function findNames() {
        initVars();
        if (inputField.value.length > 0) {
                createXMLHttpRequest();
                var url = "AutoCompleteServlet";
                xmlHttp.open("POST", url, true);
                xmlHttp.onreadystatechange = callback;
                xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                xmlHttp.send("names=" + encodeURI(inputField.value));
                //encodeURI 대신에 encodeURIComponent 를 사용해도 결과는 동일하다.
        } else {
                clearNames();
        }
 }


2. 서버에서 charset 을 UTF-8 로 설정하기

다음은 AutoCompleteServlet 서브릿 소스에서 수정할 부분을 살펴보자.

protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        String prefix = request.getParameter("names");

.
.
.


위의 doGet 메소드에 charset 을 UTF-8 으로 설정해주는 코드를 추가해준다. 그러면 브라우저에 UTF-8 방식으로 인코딩된 한글 파라미터는 같은 charset 으로 디코딩 될 것이다.

protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        String prefix = request.getParameter("names");

.
.
.

다음은 서버에서 브라우저로 응답을 보내기 전에 charset 을 UTF-8 로 바꿔줘야 한다.

response.setContentType("text/xml");

위 소스 코드를 아래와 같이 수정해 준다.

response.setContentType("text/xml;charset=UTF-8");

마지막으로 DB 조회결과를 가상으로 꾸미기 위하여 한글 데이터를 추가해 준다.

public void init(ServletConfig config) throws ServletException {
        names.add("Abe");
        names.add("Abel");
        names.add("Abigail");
        names.add("Abner");
        names.add("Abraham");
        names.add("Marcus");
        names.add("Marcy");
        names.add("Marge");
        names.add("Marie");
        names.add("스타크");
        names.add("스타크래프트");
        names.add("스타크래프트 치트키");
        names.add("스타크래프트 다운로드");
    }

수정이 끝났으면 예제를 다시 실행해 보자.

사용자 삽입 이미지

위 수정된 샘플은 다운받아 테스트해 볼 수 있다.
Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 31. 01:45

본인이 생각하는 웹서비스는 이기종간의 통신을 위한 몸부림? 이라고 생각한다.  웹서비스는 ajax 와 마찬가지로 현재 진행형이다. 이미 도입하여 이용하고 있는 곳도 있고 계속해서 버젼을 올리며 칼날을 세우고 있는 곳도 있다.

웹서비스는 어떤 언어로 구현됬는지에 상관없이 그 서비스를 사용할 수 있도록 구현해야 하는 기술로써, 이런 통신이 가능토록 하기 위한 가장 기본적인 초석은 HTTP 통신 프로토콜을 사용하고 있다는 것과 이기종간의 데이터 포맷을 고려하여 텍스트 기반의 XML 을 사용한다는 것이다.

Ajax 를 이용해서 SOAP 프로토콜을 기반으로 작성된 웹서비스에 접근할 수 있을까? 가능하지만 사실 어렵다. SOAP 프로토콜은 실제 XML 객체를 파라미터로 사용하고 있기 때문에 지금까지 우리가 해온 방식인 스트링 조합가지고는 상당히 힘들다. 왜냐하면 스트링을 조합하는 과정에서 에러가 날 수있는 확률이 매우 높기 때문에 좋은 방법은 아닐 것이다. 또한 SOAP 은 GET 방식이 아닌 POST 방식을 주로 사용해야 한다. 따라서 좀 더 쉬운 방법으로 SOAP request 을 위한 정적 XML 템플릿을 XMLHttpRequest  가 로드한 후에 DOM 속성과 메소드를 이용하여 XML 데이터를 조작하는 것이 훨씬 수월할 것이다.

 SOAP 을 이용하는 것 말고 좀 더 쉬운 방법이 존재한다. Representational State Transfer(REST) 은 웹서비스를 아주 쉽게 이용할 수 있는 서비스 이다. REST 는 SOAP 과 비교하자면 20%의 노력으로 SOAP 의 80% 에 해당하는 이득을 취할 수 있다고 한다. Ajax 는 REST 기반 웹서비스에 아주 적합하다. XHR 객체를 이용해서 REST 웹서비스에 비동기로 접근하고 그 결과는 XML 로 받아서 브라우저에서 파싱하여 사용하면 끝이다. 어떤가? 우리가 지금까지 해오던 방식과 아주 흡사하지 않은가?

여기서 한가지 더 짚고 가야 할 것은 XHR 객체의 보안 문제이다. 이 문제는 AJAX 강의 2장 - XMLHttpRequest 오브젝트 사용하기에서 언급하였다. XHR 객체가 웹서비스를 사용한다는 것은 자기가 속해 있는 도메인 밖에 있는 서버의 리소스 url 에 접근한다는 의미이므로 보안 문제는 반드시 발생하게 되어 있다. 따라서 이 문제를 해결하기 위해서는 게이트웨이 기능을 하는 프로그램을 추가로 개발해야 한다. 표현이 게이트웨이라 그럴듯해 보이지만 코딩은 굉장히 간단하다. 게이트웨이 기능은 네트웍 프로그램으로 서버쪽 프로그램에서 구현할 것이다.

이번장의 샘플 프로그램은 야후에 구현되어 있는 REST 웹서비스에 접근하여 그 결과를 보여주는 프로그램이다.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Yahoo! Search Web Services</title>

<script type="text/javascript">
var xmlHttp;

function createXMLHttpRequest() {
    if (window.ActiveXObject) {
        xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest();
    }
}
   
function doSearch() {
    var url = "YahooSearchGateway?" + createQueryString() + "&ts=" + new Date().getTime();
    createXMLHttpRequest();
    xmlHttp.onreadystatechange = handleStateChange;
    xmlHttp.open("GET", url, true);
    xmlHttp.send(null);
}

function createQueryString() {
    var searchString = document.getElementById("searchString").value;
    searchString = escape(searchString);
   
    var maxResultsCount = document.getElementById("maxResultCount").value;

    var queryString = "query=" + searchString + "&results=" + maxResultsCount;
    return queryString;
}
   
function handleStateChange() {
    if(xmlHttp.readyState == 4) {
        if(xmlHttp.status == 200) {
            parseSearchResults();
        }
        else {
            alert("Error accessing Yahoo! search");
        }
    }
}

function parseSearchResults() {
    var resultsDiv = document.getElementById("results");
    while(resultsDiv.childNodes.length > 0) {
        resultsDiv.removeChild(resultsDiv.childNodes[0]);
    }
   
    var allResults = xmlHttp.responseXML.getElementsByTagName("Result");
    var result = null;
    for(var i = 0; i < allResults.length; i++) {
        result = allResults[i];
        parseResult(result);
    }
}

function parseResult(result) {
    var resultDiv = document.createElement("div");
   
    var title = document.createElement("h3");
    title.appendChild(document.createTextNode(getChildElementText(result, "Title")));
    resultDiv.appendChild(title);
   
    var summary = document.createTextNode(getChildElementText(result, "Summary"));
    resultDiv.appendChild(summary);
   
    resultDiv.appendChild(document.createElement("br"));
    var clickHere = document.createElement("a");
    clickHere.setAttribute("href", getChildElementText(result, "ClickUrl"));
    clickHere.appendChild(document.createTextNode(getChildElementText(result, "Url")));
    resultDiv.appendChild(clickHere);
   
    document.getElementById("results").appendChild(resultDiv);
}

function getChildElementText(parentNode, childTagName) {
    var childTag = parentNode.getElementsByTagName(childTagName);
    return childTag[0].firstChild.nodeValue;
}
</script>
</head>

<body>
  <h1>Web Search Using Yahoo! Search Web Services</h1>
 
  <form action="#">
    Search String: <input type="text" id="searchString"/>
   
    <br/><br/>
    Max Number of Results:
    <select id="maxResultCount">
        <option value="1">1</option>
        <option value="10">10</option>
        <option value="25">25</option>
        <option value="50">50</option>
    </select>
   
    <br/><br/>
    <input type="button" value="Submit" onclick="doSearch();"/>
  </form>
 
  <h2>Results:</h2>
  <div id="results"/>

</body>
</html>

<yahooSearch.html 의 전체 소스 코드>

 

package ajaxbook.chap4;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.servlet.*;
import javax.servlet.http.*;

public class YahooSearchGatewayServlet extends HttpServlet {
    private static final String YAHOO_SEARCH_URL =
        "http://api.search.yahoo.com/WebSearchService/V1/webSearch?appid=thunderboltsoftware"
        + "&type=all";

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {

        String url = YAHOO_SEARCH_URL + "&" + request.getQueryString();

        HttpURLConnection con = (HttpURLConnection)new URL(url).openConnection();
        con.setDoInput(true);
        con.setDoOutput(true);

        con.setRequestMethod("GET");

        //Send back the response to the browser
        response.setStatus(con.getResponseCode());
        response.setContentType("text/xml");

        BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String input = null;
        OutputStream responseOutput = response.getOutputStream();

        while((input = reader.readLine()) != null) {
            responseOutput.write(input.getBytes());
        }

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }
}

<YahooSearchGatewayServlet.java 의 전체 소스 코드>


코드가 너무 단순한 것도 있지만 지금까지 해오던 방식 그대로이기때문에 자세한 설명은 생략하겠다. 서버쪽에 위치한 게이트웨이 프로그램은 자바를 이용해서 야후가 제공하는 REST 웹서비스 url 에 네트웍으로 접근하여 그 결과를 얻어오는 프로그램으로 굳이 자바를 사용하지 않더라도 다른 언어로 충분히 작성할 수 있을 것이다.

실핼 결과를 살펴보자.

사용자 삽입 이미지

위 그림은 yahooSearch.html 의 실행화면이다. 검색할 단어를 입력하고 검색결과 갯수를 10으로 지정한 후 Submit 버튼을 눌러보자.

사용자 삽입 이미지

위 그림은 ajax 라는 검색어를 10개의 검색결과로 지정해서 얻은 결과이다. 실제 야후 웹싸이트에 가서 ajax 라는 키워드로 검색해 보자.

사용자 삽입 이미지
위 화면은 같은 ajax 키워드로 검색한 실제 결과이다. 샘플 예제와 순서가 조금 다를뿐 제목과 내용, 그리고 링크까지 같은 결과를 보여주고 있음을 확인 할 수 있다.
Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 31. 01:42

전통적인 웹 프로그램의 경우 추가 프로세스는 다음과 같이 진행된다.
1. 전체 리스트를 조회하는 화면에서 추가 버튼 클릭
2. 추가할 항목의 세부사항을 입력받을 화면
3. 입력을 마치면 저장 버튼
4. 입력이 정상적으로 끝났다는 화면 혹은 맨 처음 전체 리스트 화면.

이것처럼 추가 프로세스를 구현하기 위해서 항목을 입력받는 페이지를 추가로 구현해야 한다. 하지만 ajax 를 이용하여 한 페이지 내에서 모든 것을 다 구현할 수 있다. 추가 프로세스가 진행되고 난 후 필요한 부분만 refresh 되는 건 당연하다. ajax 를 이용해서 추가로직만 구현해본 예제를 살펴보자.

사용자 삽입 이미지

위와 같이 직원명단을 입력하는 화면에서 필요 항목을 입력한 후 추가 버튼을 클릭하면 아래와 같이 같은 화면에 그 결과를 동적으로 반영할 수 있다. 또한 삭제 버튼을 누르면 해당 정보가 동적으로 사라진다.

사용자 삽입 이미지

그럼 해당 소스코드를 살펴보자.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Employee List</title>

<script type="text/javascript">
var xmlHttp;
var name;
var title;
var department;
var deleteID;
var EMP_PREFIX = "emp-";

function createXMLHttpRequest() {
    if (window.ActiveXObject) {
        xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest();
    }
}
   
function addEmployee() {
    name = document.getElementById("name").value;
    title = document.getElementById("title").value;
    department = document.getElementById("dept").value;
    action = "add";

    if(name == "" || title == "" || department == "") {
        return;
    }
   
    var url = "EmployeeList?"
        + createAddQueryString(name, title, department, "add")
        + "&ts=" + new Date().getTime();
       
    createXMLHttpRequest();
    xmlHttp.onreadystatechange = handleAddStateChange;
    xmlHttp.open("GET", url, true);
    xmlHttp.send(null);
}

function createAddQueryString(name, title, department, action) {
    var queryString = "name=" + name
        + "&title=" + title
        + "&department=" + department
        + "&action=" + action;
    return queryString;
}
   
function handleAddStateChange() {
    if(xmlHttp.readyState == 4) {
        if(xmlHttp.status == 200) {
            updateEmployeeList();
            clearInputBoxes();
        }
        else {
            alert("Error while adding employee.");
        }
    }
}

function clearInputBoxes() {
    document.getElementById("name").value = "";
    document.getElementById("title").value = "";
    document.getElementById("dept").value = "";
}

function deleteEmployee(id) {
    deleteID = id;
   
    var url = "EmployeeList?"
        + "action=delete"
        + "&id=" + id
        + "&ts=" + new Date().getTime();
       
    createXMLHttpRequest();
    xmlHttp.onreadystatechange = handleDeleteStateChange;
    xmlHttp.open("GET", url, true);
    xmlHttp.send(null);
}

function updateEmployeeList() {
    var responseXML = xmlHttp.responseXML;

    var status = responseXML.getElementsByTagName("status").item(0).firstChild.nodeValue;
 //var status = responseXML.getElementsByTagName("status")[0].firstChild.data;
    status = parseInt(status);
    if(status != 1) {
        return;
    }

   
    var row = document.createElement("tr");
    var uniqueID = responseXML.getElementsByTagName("uniqueID")[0].firstChild.nodeValue;
    row.setAttribute("id", EMP_PREFIX + uniqueID);
   
    row.appendChild(createCellWithText(name));
    row.appendChild(createCellWithText(title));
    row.appendChild(createCellWithText(department));
   
    var deleteButton = document.createElement("input");
    deleteButton.setAttribute("type", "button");
    deleteButton.setAttribute("value", "삭제");
    deleteButton.onclick = function () { deleteEmployee(uniqueID); };
    var cell = document.createElement("td");
    cell.appendChild(deleteButton);
    row.appendChild(cell);
   
    document.getElementById("employeeList").appendChild(row);
    updateEmployeeListVisibility();
}

function createCellWithText(text) {
    var cell = document.createElement("td");
    cell.appendChild(document.createTextNode(text));
    return cell;
}

function handleDeleteStateChange() {
    if(xmlHttp.readyState == 4) {
        if(xmlHttp.status == 200) {
            deleteEmployeeFromList();
        }
        else {
            alert("Error while deleting employee.");
        }
    }

}

function deleteEmployeeFromList() {
    //var status = xmlHttp.responseXML.getElementsByTagName("status").item(0).firstChild.nodeValue;
 var status = xmlHttp.responseXML.getElementsByTagName("status")[0].firstChild.data;
    status = parseInt(status);
    if(status != 1) {
        return;
    }
   
    var rowToDelete = document.getElementById(EMP_PREFIX + deleteID);
    var employeeList = document.getElementById("employeeList");
    employeeList.removeChild(rowToDelete);
   
    updateEmployeeListVisibility();
}

function updateEmployeeListVisibility() {
    var employeeList = document.getElementById("employeeList");
    if(employeeList.childNodes.length > 0) {
        document.getElementById("employeeListSpan").style.display = "";
    }
    else {
        document.getElementById("employeeListSpan").style.display = "none";
    }
}
</script>
</head>

<body>
  <h1>직원 명단</h1>
 
  <form action="#">
    <table width="80%" border="0">
        <tr>
            <td>이름 : <input type="text" id="name"/></td>
            <td>직위 : <input type="text" id="title"/></td>
            <td>부서 : <input type="text" id="dept"/></td>
        </tr>
        <tr>
            <td colspan="3" align="center">
                <input type="button" value="추가" onclick="addEmployee();"/>
            </td>
        </tr>
    </table>
  </form>

  <span id="employeeListSpan" style="display:none;">
  <h2>직원명부 : </h2>
 
  <table border="1" width="80%">
    <tbody id="employeeList"></tbody>
  </table>
  </span>
</body>
</html>

<employeeList.html 의 모든 소스 코드>

 

package ajaxbook.chap4;

import java.io.*;
import java.net.*;
import java.util.Random;

import javax.servlet.*;
import javax.servlet.http.*;

public class EmployeeListServlet extends HttpServlet {
   
    protected void addEmployee(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
       
        //Store the object in the database
        String uniqueID = storeEmployee();
       
        //Create the response XML
        StringBuffer xml = new StringBuffer("<result><uniqueID>");
        xml.append(uniqueID);
        xml.append("</uniqueID>");
        xml.append("<status>1</status>");
        xml.append("</result>");
       
        //Send the response back to the browser
        sendResponse(response, xml.toString());
    }
   
    protected void deleteEmployee(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
       
        String id = request.getParameter("id");
        /* Assume that a call is made to delete the employee from the database */
       
        //Create the response XML
        StringBuffer xml = new StringBuffer("<result>");
        xml.append("<status>1</status>");
        xml.append("</result>");
       
        //Send the response back to the browser
        sendResponse(response, xml.toString());
    }
   
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        String action = request.getParameter("action");
        if(action.equals("add")) {
            addEmployee(request, response);
        }
        else if(action.equals("delete")) {
            deleteEmployee(request, response);
        }
    }
   
    private String storeEmployee() {
        /* Assume that the employee is saved to a database and the
         * database creates a unique ID. Return the unique ID to the
         * calling method. In this case, make up a unique ID.
         */
        String uniqueID = "";
        Random randomizer = new Random(System.currentTimeMillis());
        for(int i = 0; i < 8; i++) {
            uniqueID += randomizer.nextInt(9);
        }
       
        return uniqueID;
    }
   
    private void sendResponse(HttpServletResponse response, String responseText)
    throws IOException {
        response.setContentType("text/xml");
        response.getWriter().write(responseText);
    }
}

<EmployeeListServlet.java 의 모든 소스 코드>

위 예제에 대한 소스를 보면 쉽게 알겠지만 직원의 정보를 입력하고 추가 버튼을 누르면 파라미터 쿼리 스트링을 생성해서 GET/비동기 방식으로 서버로 보낸다. 서버에서는 해당 정보를 DB 에 insert 가상 로직을 수행하고 그 결과를 XML 로 만들어서 클라이언트로 전송한다. XHR 객체는 해당 XML 정보로 부터 정보를 다시 추출하고 테이블에 추가하는 형태의 흐름으로 전개된다. 삭제 로직도 추가 로직과 거의 비슷하게 동작한다.

위 예제중에 한가지 살펴볼 것이 있다.

deleteButton.onclick = function () { deleteEmployee(uniqueID); };

이 부분의 코드를 다음과 같이 사용할 수도 있을 것이다.

deleteButton.setAttibute("onclick", "deleteEmployee('"+uniqueID+"');");

하지만 IE 및 FireFox 두개의 브라우저에서 제대로 작동을 하지 않았다. 하지만 다행히 처음코드를 사용하면 모두 정상적으로 잘 작동 하였다. AJAX 를 사용하면서 가장 큰 골치거리가 바로 이런 코드이다. 하루빨리 W3C 표준을 구현하도록 브라우저 전영에도 각성이 촉구된다.

Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 31. 01:39

웹 페이지에서 툴팁이란 어떤 것일까?

아래 그림을 보자. 마우스를 해당 이미지에 갖다 대면 그 그림에 대한 세부 설명이 나오는 것을 봤을 것이다. 이런 기능을 ajax 을 이용해서 구현할 수 있다.


사용자 삽입 이미지
 
 
보잘 것 없지만 기능을 단순화 시킨 예제를 살펴보자.
 
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>Ajax Tooltip</title>
    <script type="text/javascript">       
        var xmlHttp;
        var dataDiv;
        var dataTable;
        var dataTableBody;
        var offsetEl;
        function createXMLHttpRequest() {
            if (window.ActiveXObject) {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            else if (window.XMLHttpRequest) {
                xmlHttp = new XMLHttpRequest();               
            }
        }
        function initVars() {
            dataTableBody = document.getElementById("courseDataBody");           
            dataTable = document.getElementById("courseData");
            dataDiv = document.getElementById("popup");
        }
        function getCourseData(element) {
            initVars();           
            createXMLHttpRequest();
            offsetEl = element;
            var url = "ToolTipServlet?key=" + escape(element.id);
           
            xmlHttp.open("GET", url, true);
            xmlHttp.onreadystatechange = callback;
            xmlHttp.send(null);
        }
        function callback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {
                    setData(xmlHttp.responseXML);
                }
            }
        }
       
        function setData(courseData) {           
            clearData();
            setOffsets();
            var length = courseData.getElementsByTagName("length")[0].firstChild.data;
            var par = courseData.getElementsByTagName("par")[0].firstChild.data;
           
            var row, row2;
            var parData = "Par: " + par
            var lengthData = "Length: " + length;
           
            row = createRow(parData);           
            row2 = createRow(lengthData);
           
            dataTableBody.appendChild(row);
            dataTableBody.appendChild(row2);
        }
        function createRow(data) {           
            var row, cell, txtNode;
            row = document.createElement("tr");
            cell = document.createElement("td");
            cell.setAttribute("bgcolor", "#FFFAFA");
            cell.setAttribute("border", "0");                          
            txtNode = document.createTextNode(data);
            cell.appendChild(txtNode);
            row.appendChild(cell);
          
            return row; 
        }
       
        function setOffsets() {
            var end = offsetEl.offsetWidth;
            var top = calculateOffsetTop(offsetEl);
            dataDiv.style.border = "black 1px solid";
            dataDiv.style.left = end + 15 + "px";
            dataDiv.style.top = top + "px";
        }
       
        function calculateOffsetTop(field) {
          return calculateOffset(field, "offsetTop");
        }
        function calculateOffset(field, attr) {
          var offset = 0;
          while(field) {
            offset += field[attr];
            field = field.offsetParent;
          }
          return offset;
        }
        function clearData() {           
            var ind = dataTableBody.childNodes.length;           
            for (var i = ind - 1; i >= 0 ; i--) {
                dataTableBody.removeChild(dataTableBody.childNodes[i]);      
            }
            dataDiv.style.border = "none";
        }       
    </script>
  </head>
  <body>
    <h1>Ajax Tooltip Example</h1>
    <h3>Golf Courses</h3>
    <table id="courses" bgcolor="#FFFAFA" border="1" cellspacing="0" cellpadding="2"/>
        <tbody>
            <tr><td id="1" onmouseover="getCourseData(this);" onmouseout="clearData();">Augusta National</td></tr>
            <tr><td id="2" onmouseover="getCourseData(this);" onmouseout="clearData();">Pinehurst No. 2</td></tr>
            <tr><td id="3" onmouseover="getCourseData(this);" onmouseout="clearData();">St. Andrews Links</td></tr>
            <tr><td id="4" onmouseover="getCourseData(this);" onmouseout="clearData();">Baltusrol Golf Club</td></tr>
        </tbody>       
    </table>
    <div style="position:absolute;" id="popup">
        <table id="courseData" bgcolor="#FFFAFA" border="0" cellspacing="2" cellpadding="2"/>
            <tbody id="courseDataBody"></tbody>
        </table>
    </div>
  </body>
</html>
<toolTip.html 의 전체 소스>
 
 
package ajaxbook.chap4;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.*;
import javax.servlet.http.*;
/**
 *
 * @author nate
 * @version
 */
public class ToolTipServlet extends HttpServlet {
   
    private Map courses = new HashMap();
    public void init(ServletConfig config) throws ServletException {
        CourseData augusta = new CourseData(72, 7290);
        CourseData pinehurst = new CourseData(70, 7214);
        CourseData standrews = new CourseData(72, 6566);
        CourseData baltusrol = new CourseData(70, 7392);
        courses.put(new Integer(1), augusta);
        courses.put(new Integer(2), pinehurst);
        courses.put(new Integer(3), standrews);
        courses.put(new Integer(4), baltusrol);
    }
   
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        Integer key = Integer.valueOf(request.getParameter("key"));
        CourseData data = (CourseData) courses.get(key);
       
        PrintWriter out = response.getWriter();
        response.setContentType("text/xml");
        response.setHeader("Cache-Control", "no-cache");
        out.println("<response>");
        out.println("<par>" + data.getPar() + "</par>");
        out.println("<length>" + data.getLength() + "</length>");
        out.println("</response>");
        out.close();
    }
   
    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        doGet(request, response);
    }
       
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Short description";
    }
   
    private class CourseData {
        private int par;
        private int length;
       
        public CourseData(int par, int length) {
            this.par = par;
            this.length = length;
        }
       
        public int getPar() {
            return this.par;
        }
       
        public int getLength() {
            return this.length;
        }
    }
}
 
<ToolTipServlet.java 의 전체 소스 코드>
 
 
 
이전 예제와 다른 점은 옵셋을 구현한 코드이다.
 
function calculateOffset(field, attr) {
    var offset = 0;
    while(field) {
        offset += field[attr];
        field = field.offsetParent;
    }
    return offset;
}
 
이 코드는 브라우저 마다 작동을 안할 수 있지만, 테스트 해본 결과 IE 와 firefox 에서는 잘 실행되었다. 위 예제를 실행해 본 결과는 아래와 같다.

 
사용자 삽입 이미지
 
Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 31. 01:37

웹 프로그램의 경우 요청에 대한 응답이 제대로 오지 않거나 너무 많은 시간이 걸린다면 사용자는 불평을 하면서 다른 싸이트로 가버리고 다시는 그 싸이트를 이용하지 않을 것이다. 혹은 금전적인 문제가 걸려있다면 서비스 콜센터로 항의 전화를 할 것이다.

2년전 국내 모 은행 인터넷 뱅킹을 개발하면서 로그인 기능을 구현해 놓을 것을 살펴보고 놀라지 않을 수 없었다. 로그인을 하는데 자그마치 30초가 걸렸다. 웹 프로그램에서 30초라...어떻게 생각하는가? 당연히 설계가 잘못됬다. 고객에게 이런 불편을 초래하는 싸이트라면 경쟁 인터넷뱅킹 은행에게 모든 고객을 빼앗길 것이다.

 몇년전의 통계수치라 지금은 기억이 잘 나지 않지만, 인터넷 이용자가 응답정보를 기다리다 인내심을 더이상 참지 못하고 다른 싸이트로 가버리는 시간은 7~8초 였던 것으로 기억한다. 하지만 지금은 좀더 짧아지지 않았을까 생각한다.

30초는 정말 말도 안되는 시간이지만 5~6초정도의 시간이면 사용자에게 현재 서버의 작업 진행상태를 표시해주면 사용자는 안도감을 느끼면서 좀 더 기다려줄 확률은 높아질 수 있다. 이것은 일종의 서비스 차원의 아이디어일뿐이지, 사실 서버의 프로그램을 수정해서 최대한 프로세스를 단축하는 것이 가장 좋은 해결책이라 하겠다.

로그인 하는데 30초 걸렸던 그 프로젝트에서는 할 수 없이 서버의 상태바를 이미지로 붙여 놓았다. 이런 이미지는 사실 의미가 없는것이 현재 서버의 상태를 실시간으로 보여주는 것이 아니라 반복적인 이미지로 하여금 고객을 짜증나게 할 수도 있다. 실제 서버의 작업 진척도와 현재 브라우저에서 보여주는 정보가 일치해야 진정한 상태바 프로그램이라 할 수 있겠다.

상태바 프로그램을 위해서는 자원의 소모라는 손해를 감수해야만 한다. 서버의 비지니스 로직이 현재 얼마나 진행됬는지 별도로 체크해야 하는 코딩이 추가되어야 한다. 가뜩이나 시간이 오래걸리는 작업인데 그 작업이 얼마나 진행됬는지 확인하는 로직이 추가된다면 자원의 소모가 그만큼 더 많아지게 되고 시간도 더 지연되는 것이다. 장점이 있다면 단점이 있기 마련이다. 장점을 더욱 부각시키고 단점을 줄이는 방향으로 생각을 전환하자.

이런 복잡한 문제에서 벗어나 지금은 상태바에 대한 구현 자체가 목적이므로 단순한 예제를 통해서 살펴보기로 하자.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
    <title>Ajax Progress Bar</title>
    <script type="text/javascript">
        var xmlHttp;
        var key;
        var bar_color = 'gray';
        var span_id = "block";
        var clear = "&nbsp;&nbsp;&nbsp;"

        function createXMLHttpRequest() {
            if (window.ActiveXObject) {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            else if (window.XMLHttpRequest) {
                xmlHttp = new XMLHttpRequest();               
            }
        }

        function go() {
            createXMLHttpRequest();
            checkDiv();
            var url = "ProgressBarServlet?task=create";
            var button = document.getElementById("go");
            button.disabled = true;
            xmlHttp.open("GET", url, true);
            xmlHttp.onreadystatechange = goCallback;
            xmlHttp.send(null);
        }

        function goCallback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {
                    setTimeout("pollServer()", 1000);
                }
            }
        }
       
        function pollServer() {
            createXMLHttpRequest();
            var url = "ProgressBarServlet?task=poll";
            xmlHttp.open("GET", url, true);
            xmlHttp.onreadystatechange = pollCallback;
            xmlHttp.send(null);
        }
       
        function pollCallback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {
                    var percent_complete = xmlHttp.responseXML.getElementsByTagName("percent")[0].firstChild.data;
                   
                    var index = processResult(percent_complete);
                    for (var i = 1; i <= index; i++) {
                        var elem = document.getElementById("block" + i);
                        elem.innerHTML = clear;

                        elem.style.backgroundColor = bar_color;
                        var next_cell = i + 1;
                        if (next_cell > index && next_cell <= 9) {
                            document.getElementById("block" + next_cell).innerHTML = percent_complete + "%";
                        }
                    }
                    if (index < 9) {
                        setTimeout("pollServer()", 1000);
                    } else {
                        document.getElementById("complete").innerHTML = "Complete!";
                        document.getElementById("go").disabled = false;
                    }
                }
            }
        }
       
        function processResult(percent_complete) {
            var ind;
            if (percent_complete.length == 1) {
                ind = 1;
            } else if (percent_complete.length == 2) {
                ind = percent_complete.substring(0, 1);
            } else {
                ind = 9;
            }
            return ind;
        }

        function checkDiv() {
            var progress_bar = document.getElementById("progressBar");
            if (progress_bar.style.visibility == "visible") {
                clearBar();
                document.getElementById("complete").innerHTML = "";
            } else {
                progress_bar.style.visibility = "visible"
            }
        }
       
        function clearBar() {
            for (var i = 1; i < 10; i++) {
                var elem = document.getElementById("block" + i);
                elem.innerHTML = clear;
                elem.style.backgroundColor = "white";
            }
        }
    </script>
  </head>
  <body>
    <h1>Ajax Progress Bar Example</h1>
    Launch long-running process: <input type="button" value="Launch" id="go" onclick="go();"/>
    <p>
    <table align="center">
        <tbody>
            <tr><td>
                <div id="progressBar" style="padding:2px;border:solid black 2px;visibility:hidden">
                    <span id="block1">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block2">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block3">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block4">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block5">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block6">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block7">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block8">&nbsp;&nbsp;&nbsp;</span>
                    <span id="block9">&nbsp;&nbsp;&nbsp;</span>
                </div>
            </td></tr>
            <tr><td align="center" id="complete"></td></tr>
        </tbody>
    </table>
  </body>
</html>

<progressBar.html 의 전체 소스 코드>

 

package ajaxbook.chap4;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

/**
 *
 * @author nate
 * @version
 */
public class ProgressBarServlet extends HttpServlet {
    private int counter = 1;
   
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        String task = request.getParameter("task");
        String res = "";
       
        if (task.equals("create")) {
            res = "<key>1</key>";
            counter = 1;
        }
        else {
            String percent = "";
            switch (counter) {
                case 1: percent = "10"; break;
                case 2: percent = "23"; break;
                case 3: percent = "35"; break;
                case 4: percent = "51"; break;
                case 5: percent = "64"; break;
                case 6: percent = "73"; break;
                case 7: percent = "89"; break;
                case 8: percent = "100"; break;
            }
            counter++;
               
            res = "<percent>" + percent + "</percent>";
        }
       
        PrintWriter out = response.getWriter();
        response.setContentType("text/xml");
        response.setHeader("Cache-Control", "no-cache");
        out.println("<response>");
        out.println(res);
        out.println("</response>");
        out.close();
    }
   
    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        doGet(request, response);
    }
   
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Short description";
    }
}

<ProgressBarServlet.java 의 전체 소스 코드>

우선 실행 결과 화면을 먼저 살펴보자.


사용자 삽입 이미지

위 그리에서 Launch 버튼을 클릭하면 아래와 같은 상태바 결과를 확인 할 수 있다.


사용자 삽입 이미지
사용자 삽입 이미지

이런 상태바 프로그램을 작성하는데에는 ajax 가 정말 적합하다고 생각한다. 물론 자바스크립트 코드가 많아져서 이해하는데 어려움이 있을 수도 있지만 ajax 을 사용하지 않고 다른 방식으로 구현한다고 했을때 가능은 하겠지만 이때에도 역시 스크립팅 코드가 없을 수 만은 없다.

소스 코드는 이전예제와 별로 크게 다르지 않으므로 설명은 생략한다.

Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 26. 23:45

auto refresh 기능, 즉 자동으로 페이지의 일정부분을 지정한 시간마다 정보를 업데이트 해주는 기능이다.

우선 이런 기능을 이미 구현해 놓은 싸이트들을 살펴보자.

사용자 삽입 이미지

<최근 소식을 ajax 를 이용하여 소팅 기능을 구현한 싸이트(www.digg.com/spy)>


사용자 삽입 이미지

<다운로드 카운트를 ajax 를 이용하여 구현한 싸이트(www.apple.com/itunes)>



위 두 싸이트를 살펴보면 일정시간을 설정하여 지속적으로 해당 부분만 정보가 수정되는 것을 확인 할 수 있다. 이번 주제 역시 ajax 의 전형적인 특징으로 전체 페이지가 새로고침되지 않고 필요한 일정부분만 정보가 수정된다는데 있다.


단순한 예제를 통해서 auto refresh 기능에 대해서 생각해 보자


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
    <title>Ajax Dynamic Update</title>
    <script type="text/javascript">
        var xmlHttp;

        function createXMLHttpRequest() {
            if (window.ActiveXObject) {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            else if (window.XMLHttpRequest) {
                xmlHttp = new XMLHttpRequest();               
            }
        }
       
        function doStart() {
            createXMLHttpRequest();
            var url = "DynamicUpdateServlet?task=reset";
            xmlHttp.open("GET", url, true);
            xmlHttp.onreadystatechange = startCallback;
            xmlHttp.send(null);
        }

        function startCallback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {                   
                    setTimeout("pollServer()", 5000);
                    refreshTime();
                }
            }
        }
       
        function pollServer() {
            createXMLHttpRequest();
            var url = "DynamicUpdateServlet?task=foo";
            xmlHttp.open("GET", url, true);
            xmlHttp.onreadystatechange = pollCallback;
            xmlHttp.send(null);
        }
       
        function refreshTime(){
            var time_span = document.getElementById("time");
            var time_val = time_span.innerHTML;

            var int_val = parseInt(time_val);
            var new_int_val = int_val - 1;
           
            if (new_int_val > -1) {
                setTimeout("refreshTime()", 1000);
                time_span.innerHTML = new_int_val;               
            } else {
                time_span.innerHTML = 5;
            }
        }
       
        function pollCallback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {               
                    var message = xmlHttp.responseXML.getElementsByTagName("message")[0].firstChild.data;                   
                   
                    if (message != "done") {
                        var new_row = createRow(message);
                        var table = document.getElementById("dynamicUpdateArea");
                        var table_body = table.getElementsByTagName("tbody").item(0);
                        var first_row = table_body.getElementsByTagName("tr").item(1);
                        table_body.insertBefore(new_row, first_row);                       
                        setTimeout("pollServer()", 5000);
                        refreshTime();
                    }
                }
            }
        }
       
        function createRow(message) {
            var row = document.createElement("tr");
            var cell = document.createElement("td");
            var cell_data = document.createTextNode(message);
            cell.appendChild(cell_data);
            row.appendChild(cell);
            return row;
        }
    </script>
  </head>
  <body>
    <h1>Ajax Dynamic Update Example</h1>
    This page will automatically update itself:
        <input type="button" value="Launch" id="go" onclick="doStart();"/>
    <p>
    Page will refresh in <span id="time">5</span> seconds.
    <p>
    <table id="dynamicUpdateArea" align="left">
        <tbody>
            <tr id="row0"><td></td></tr>
        </tbody>
    </table>
  </body>
</html>

<dynamicUpdate.html 의 전체 소스 코드>

 

 

package ajaxbook.chap4;

import java.io.*;
import java.net.*;

import javax.servlet.*;
import javax.servlet.http.*;

/**
 *
 * @author nate
 * @version
 */
public class DynamicUpdateServlet extends HttpServlet {
    private int counter = 1;
   
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        String res = "";
        String task = request.getParameter("task");
        String message = "";       
       
        if (task.equals("reset")) {
            counter = 1;
        } else {
            switch (counter) {
                case 1: message = "Steve walks on stage"; break;
                case 2: message = "iPods rock"; break;
                case 3: message = "Steve says Macs rule"; break;
                case 4: message = "Change is coming"; break;
                case 5: message = "Yes, OS X runs on Intel - has for years"; break;
                case 6: message = "Macs will soon have Intel chips"; break;
                case 7: message = "done"; break;
            }
            counter++;
        }

        res = "<message>" + message + "</message>";
       
        PrintWriter out = response.getWriter();
        response.setContentType("text/xml");
        response.setHeader("Cache-Control", "no-cache");
        out.println("<response>");
        out.println(res);
        out.println("</response>");
        out.close();
    }
   
    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        doGet(request, response);
    }
   

    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Short description";
    }
}

<DynamicUpdateServlet 의 전체 소스 코드>

 

이번 예제는 실행결과 화면을 먼저 보고 설명하는 것이 좋을 듯 싶다.


사용자 삽입 이미지

위 그림은 dynamicUpdate.html 화면인데 Launch 버튼을 클릭하면 아래 화면의 일정부분이 refresh 될 것이다.


사용자 삽입 이미지

사실 seconds 가 5, 4, 3, 2, 1 으로 값이 줄어드는 것은 소스를 보면 알겠지만 자바스크립트로 처리한 것이고 중요하게 볼것은 그 아래에 있는 문자열이다. 5초가 지날때마다 브라우저는 서버에 비동기적으로 접속해 관련 데이터를 추출하여 추가해주는 부분이다.


소스는 그리 길지는 않지만 dynamicUpdate.html 소스는 스크립팅 처리가 좀 들어가 있다. 하지만 지금까지의 강의 내용을 잘 읽어 봤다면 처음나오는 코드나 이해가지 않는 곳은 없으리라 생각한다.

Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 26. 23:39

전통적으로 웹 프로그램밍은 사용자 입력에 의존한 step by step 어플리케이션에 매우 적합한 방식이다. 그 만큼 이전 페이지와 이후 페이지 사이에서의 사용자 입력이 중요하다. 어떻게 보면 이런 step by step 방식의 페이지 구성은 웹이 아닌 다른 프로그램과 비교해봤을때 어떻게 보면 단순하고 멍청해보이기까지 한다. AJAX 가 도입되면서 전통적인 웹 프로그램에 조금씩 수정이 가해지고 있는데 이번장에서는 지금까지 배운 내용만을 토대로 두개의 select box 값이 파라미터가 되어 매칭되는 결과를 동적으로 보여주는 예제를 소개하기로 한다.

사실 AJAX 를 사용하지 않더라도 이런 효과는 이전에도 가능했다. select box 의 value 값과 매칭결과를 한꺼번에 받아 hidden 요소나 혹은 다른 방식으로 웹페이지에 저장했다가 사용자의 입력에 따라 그 부분만 취사선택해서 보여주면 된다. 하지만 이경우 데이타가 많아지면 페이지 로딩시간이 지연되고 사용자가 급증할수록 서버에 무리가 갈 것이다. 그리고 일반적으로 클라이언트 보다는 서버 머신의 성능이 월등하므로 많은 데이터를 서버로부터 한꺼번에 받아서 브라우저에서 스크립팅언어로 데이터를 분석해서 보여주는 것은 역시 비효율적이다.

따라서 이번에는 사용자의 입력이 있을때마다 비동기 방식으로 서버에 접속해 그 결과를 바로 알 수 있는 예제를 살펴보자. AJAX 의 가장 큰 장점 중의 하나는 역시 전체 페이지 로딩이 필요 없다는 것이다. 물론 전체 페이지 로딩이 필요한 경우도 있지만, 그렇지 않은 경우에도 전체 페이지가 로딩된다는 것은 결국 서버와 클라이언트 모두 불필요한 작업을 계속 해야 된다는 것을 의미한다. 서버쪽 비지니스로직 프로그램을 해본 개발자는 잘 알 것이다. 서버의 성능을 판단하는 지표중에 TPS 라고 있다. 프로젝트를 진행하면서 구현이 끝나면 전체 성능 및 로드 테스트를 진행하는데 1 TPS 올리는 것이 얼마나 어려운지 안 해본 사람은 모를 것이다. 자원을 좀더 효율적으로 사용해보자. 그리고 좀 더 스마트하게 웹기술을 적용하게 보자.


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Dynamically Filling Lists</title>

<script type="text/javascript">
var xmlHttp;

function createXMLHttpRequest() {
    if (window.ActiveXObject) {
        xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest();
    }
}
   
function refreshModelList() {
    var make = document.getElementById("make").value;
    var modelYear = document.getElementById("modelYear").value;

    if(make == "" || modelYear == "") {
        clearModelsList();
        return;
    }
   
    var url = "RefreshModelList?"
        + createQueryString(make, modelYear) + "&ts=" + new Date().getTime();
       
    createXMLHttpRequest();
    xmlHttp.onreadystatechange = handleStateChange;
    xmlHttp.open("GET", url, true);
    xmlHttp.send(null);
}

function createQueryString(make, modelYear) {
    var queryString = "make=" + make + "&modelYear=" + modelYear;
    return queryString;
}
   
function handleStateChange() {
    if(xmlHttp.readyState == 4) {
        if(xmlHttp.status == 200) {
            updateModelsList();
        }
    }
}

function updateModelsList() {
    clearModelsList();
   
    var models = document.getElementById("models");
    var results = xmlHttp.responseXML.getElementsByTagName("model");
    var option = null;
    for(var i = 0; i < results.length; i++) {
        option = document.createElement("option");
        option.appendChild(document.createTextNode(results[i].firstChild.nodeValue));
        models.appendChild(option);
    }
}

function clearModelsList() {
    var models = document.getElementById("models");
    while(models.childNodes.length > 0) {
        models.removeChild(models.childNodes[0]);
    }
}
</script>
</head>

<body>
  <h1>Select Model Year and Make</h1>
 
  <form action="#">
    <span style="font-weight:bold;">Model Year:</span>
    <select id="modelYear" onchange="refreshModelList();">
        <option value="">Select One</option>
        <option value="2006">2006</option>
        <option value="1995">1995</option>
        <option value="1985">1985</option>
        <option value="1970">1970</option>
    </select>
   
    <br/><br/>
    <span style="font-weight:bold;">Make:</span>
    <select id="make" onchange="refreshModelList();">
        <option value="">Select One</option>
        <option value="Chevrolet">Chevrolet</option>
        <option value="Dodge">Dodge</option>
        <option value="Pontiac">Pontiac</option>
    </select>
   
    <br/><br/>
    <span style="font-weight:bold;">Models:</span>
    <br/>
    <select id="models" size="6" style="width:300px;">
   
    </select>
 
  </form>

</body>
</html>

<dynamicLists.html 의 전체 소스>


package ajaxbook.chap4;

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.servlet.*;
import javax.servlet.http.*;

public class RefreshModelListServlet extends HttpServlet {

    private static List availableModels = new ArrayList();
   
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
       
        int modelYear = Integer.parseInt(request.getParameter("modelYear"));
        String make = request.getParameter("make");
       
        StringBuffer results = new StringBuffer("<models>");
        MakeModelYear availableModel = null;
        for(Iterator it = availableModels.iterator(); it.hasNext();) {
            availableModel = (MakeModelYear)it.next();
            if(availableModel.modelYear == modelYear) {
                if(availableModel.make.equals(make)) {
                    results.append("<model>");
                    results.append(availableModel.model);
                    results.append("</model>");
                }
            }
        }
        results.append("</models>");
       
        response.setContentType("text/xml");
        response.getWriter().write(results.toString());
    }
   
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }
   
    public void init() throws ServletException {
        availableModels.add(new MakeModelYear(2006, "Dodge", "Charger"));
        availableModels.add(new MakeModelYear(2006, "Dodge", "Magnum"));
        availableModels.add(new MakeModelYear(2006, "Dodge", "Ram"));
        availableModels.add(new MakeModelYear(2006, "Dodge", "Viper"));
        availableModels.add(new MakeModelYear(1995, "Dodge", "Avenger"));
        availableModels.add(new MakeModelYear(1995, "Dodge", "Intrepid"));
        availableModels.add(new MakeModelYear(1995, "Dodge", "Neon"));
        availableModels.add(new MakeModelYear(1995, "Dodge", "Spirit"));
        availableModels.add(new MakeModelYear(1985, "Dodge", "Aries"));
        availableModels.add(new MakeModelYear(1985, "Dodge", "Daytona"));
        availableModels.add(new MakeModelYear(1985, "Dodge", "Diplomat"));
        availableModels.add(new MakeModelYear(1985, "Dodge", "Omni"));
        availableModels.add(new MakeModelYear(1970, "Dodge", "Challenger"));
        availableModels.add(new MakeModelYear(1970, "Dodge", "Charger"));
        availableModels.add(new MakeModelYear(1970, "Dodge", "Coronet"));
        availableModels.add(new MakeModelYear(1970, "Dodge", "Dart"));

        availableModels.add(new MakeModelYear(2006, "Chevrolet", "Colorado"));
        availableModels.add(new MakeModelYear(2006, "Chevrolet", "Corvette"));
        availableModels.add(new MakeModelYear(2006, "Chevrolet", "Equinox"));
        availableModels.add(new MakeModelYear(2006, "Chevrolet", "Monte Carlo"));
        availableModels.add(new MakeModelYear(1995, "Chevrolet", "Beretta"));
        availableModels.add(new MakeModelYear(1995, "Chevrolet", "Camaro"));
        availableModels.add(new MakeModelYear(1995, "Chevrolet", "Cavalier"));
        availableModels.add(new MakeModelYear(1995, "Chevrolet", "Lumina"));
        availableModels.add(new MakeModelYear(1985, "Chevrolet", "Cavalier"));
        availableModels.add(new MakeModelYear(1985, "Chevrolet", "Chevette"));
        availableModels.add(new MakeModelYear(1985, "Chevrolet", "Celebrity"));
        availableModels.add(new MakeModelYear(1985, "Chevrolet", "Citation II"));
        availableModels.add(new MakeModelYear(1970, "Chevrolet", "Bel Air"));
        availableModels.add(new MakeModelYear(1970, "Chevrolet", "Caprice"));
        availableModels.add(new MakeModelYear(1970, "Chevrolet", "Chevelle"));
        availableModels.add(new MakeModelYear(1970, "Chevrolet", "Monte Carlo"));

        availableModels.add(new MakeModelYear(2006, "Pontiac", "G6"));
        availableModels.add(new MakeModelYear(2006, "Pontiac", "Grand Prix"));
        availableModels.add(new MakeModelYear(2006, "Pontiac", "Solstice"));
        availableModels.add(new MakeModelYear(2006, "Pontiac", "Vibe"));
        availableModels.add(new MakeModelYear(1995, "Pontiac", "Bonneville"));
        availableModels.add(new MakeModelYear(1995, "Pontiac", "Grand Am"));
        availableModels.add(new MakeModelYear(1995, "Pontiac", "Grand Prix"));
        availableModels.add(new MakeModelYear(1995, "Pontiac", "Firebird"));
        availableModels.add(new MakeModelYear(1985, "Pontiac", "6000"));
        availableModels.add(new MakeModelYear(1985, "Pontiac", "Fiero"));
        availableModels.add(new MakeModelYear(1985, "Pontiac", "Grand Prix"));
        availableModels.add(new MakeModelYear(1985, "Pontiac", "Parisienne"));
        availableModels.add(new MakeModelYear(1970, "Pontiac", "Catalina"));
        availableModels.add(new MakeModelYear(1970, "Pontiac", "GTO"));
        availableModels.add(new MakeModelYear(1970, "Pontiac", "LeMans"));
        availableModels.add(new MakeModelYear(1970, "Pontiac", "Tempest"));
    }

    private static class MakeModelYear {
        private int modelYear;
        private String make;
        private String model;
       
        public MakeModelYear(int modelYear, String make, String model) {
            this.modelYear = modelYear;
            this.make = make;
            this.model = model;
        }
    }
}

<RefreshModelListServlet.java 의 전체 소스>

사실 위 샘플예제 또한 솔직히 추가적인 설명은 하지 않겠다. 만일 자바를 모르는 사람이라면 서버쪽 프로그램의 코드 하나하나를 이해하려고 할 필요는 없을 듯 싶다. 핵심은 ajax 이다. 이번 예제의 서버 프로그램이 길어지게 된 이유는, 사실 비지니스 로직에서는 DB 데이터를 분석하여 매칭되는 결과를 브라우저로 보내줘야 하지만 그렇게 되면 서버 프로그램이 더 복잡해지므로 샘플의 단순화를 위해 좀 무식하게 작성하였다.

사용자 삽입 이미지

<연도와 제조회사 선택여부에 따라서 맨 아래 모델 종류가 동적으로 바뀌는 화면>

 

Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 26. 23:37

웹 프로그래밍에서는 브라우저의 요청에 응답을 해야 한다. 정상적인 응답이든 비정상이든 브라우저는 그 결과를 표시한다. 이번 주제는 AJAX 를 이용하여 서버의 상태만을 확인해 볼 수 있는 방법을 제시하고자 한다. 서버의 상태를 확인하기 위해서는 특정 리소스 url 로 요청을 보내면 된다. 하지만 서버는 응답정보를 브라우저에 보내게 되는데, 이럴경우에 응답정보의 모든 부분이 필요하지는 않는다. 단지 헤더정보만 얻을 수 있으면 서버의 상태를 파악할 수 있다.

지금까지 XMLHttpRequest 객체의 open(method, url, asynch) 메소드의 method 에는 GET 및 POST 만을 사용했었다. 하지만 HEAD 을 사용하면, 즉 브라우저가 HEAD 요청을 보내면, HEAD 요청을 받은 서버는 응답을 보낼때 body 의 내용은 빼버리고 헤더 정보만 채워서 보내게 된다. 따라서 오고가는 정보의 양이 극히 줄기때문에 브라우저에서 시간간격으로 서버의 상태를 점검하는데 아주 유용하게 사용할 수 있다.

이번 주제의 예제를 살펴보자.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Reading Response Headers</title>

<script type="text/javascript">
var xmlHttp;
var requestType = "";

function createXMLHttpRequest() {
    if (window.ActiveXObject) {
        xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else if (window.XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest();
    }
}
   
function doHeadRequest(request, url) {
    requestType = request;
    createXMLHttpRequest();
    xmlHttp.onreadystatechange = handleStateChange;
    xmlHttp.open("HEAD", url, true);
    xmlHttp.send(null);
}
   
function handleStateChange() {
    if(xmlHttp.readyState == 4) {
        if(requestType == "allResponseHeaders") {
            getAllResponseHeaders();
        }
        else if(requestType == "lastModified") {
            getLastModified();
        }
        else if(requestType == "isResourceAvailable") {
            getIsResourceAvailable();
        }
    }
}

function getAllResponseHeaders() {
    alert(xmlHttp.getAllResponseHeaders());
}

function getLastModified() {
    alert("Last Modified: " + xmlHttp.getResponseHeader("Last-Modified"));
}

function getIsResourceAvailable() {
    if(xmlHttp.status == 200) {
        alert("Successful response");
    }
    else if(xmlHttp.status == 404) {
        alert("Resource is unavailable");
    }
    else {
        alert("Unexpected response status: " + xmlHttp.status);
    }
}

</script>
</head>

<body>
  <h1>Reading Response Headers</h1>
 
  <a href="javascript:doHeadRequest('allResponseHeaders', 'readingResponseHeaders.xml');">Read All Response Headers</a>
 
  <br/>
  <a href="javascript:doHeadRequest('lastModified', 'readingResponseHeaders.xml');">Get Last Modified Date</a>
 
  <br/>
  <a href="javascript:doHeadRequest('isResourceAvailable', 'readingResponseHeaders.xml');">Read Available Resource</a>
 
  <br/>
  <a href="javascript:doHeadRequest('isResourceAvailable', 'not-available.xml');">Read Unavailable Resource</a>

</body>
</html>

<readingResponseHeaders.html 의 전체 소스>

 

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

<readingResponseHeaders>

</readingResponseHeaders>

<readingResponseHeaders.xml 의 전체 소스>

지금까지 GET 혹은 POST 방식만을 사용해 예제를 작성했던 생각을 떠올려 볼때 이번 예제의 유일한 차이점은 xmlHttp.open("HEAD", url, true); 이다. HEAD 는 서버에 요청을 보낼때 HEAD 요청을 보내는 것으로 요청을 받은 서버는 body 의 정보를 아무것도 채우지 않고 단지 헤더 정보만을 넣어서 브라우저로 보내게 되다.

의심이 간다면 readingResponseHeaders.xml 파일에 내용을 채운후 아래와 같이 코드를 수정해서 실행해보자.

function getAllResponseHeaders() {
    alert(xmlHttp.getAllResponseHeaders());
    alert(xmlHttp.responseText);
    alert(xmlHttp.responseXML.getElementsByTagName("readingResponseHeaders")[0].firstChild.nodeValue);
}

첫번째 alert() 만 헤더정보를 표시하고 두번째, 세번째는 아무런 아무런 값도 없을 것이다.(open 메소드의 HEAD 를 GET 으로 수정한 후 다시 실행해 보면 두번째, 세번째 모두 값을 출력할 것이다.)

따라서 서버의 상태만을 점검하는 것이라면 HEAD 요청을 사용하는 것이 브라우저와 서버간 오고가는 데이터의 양이 매우 적어지기 때문에 서버에 부하를 최소화 하는 방식이라고 하겠다.


사용자 삽입 이미지

<서버의 응답정보 중 헤더정보만을 표시한 그림>


사용자 삽입 이미지

<서버의 헤더정보중 Last-Modified 항목에 대한 값>


사용자 삽입 이미지
 
<XHR 객체의 state 상태가 200 일 경우>
 
 
참고로 XHR 객체의 state 의 값이 200 이면 정상적인 서버 응답종료를 의미한다. 404는 해당 서버 리소스 url 이 존재하지 않음을 의미한다. 500이면 서버가 해당 요청을 수행하다가 내부적으로 에러가 발생했음을 의미한다.

사용자 삽입 이미지

<XHR 객체의 state 상태가 404 일 경우>

Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 26. 23:30

3장에서는 AJAX 의 여러가지 기본적인 특성에 대해서 공부하였다. 또한 아주 간단한 예제를 통해서 그 특징들이 어떻게 적용되는지도 살펴보았다. 이번 4장에서는 좀더 발전하여 실제적으로 쓰임새 측면에서 AJAX 를 다루어 보기로 한다.


실용적인 측면에서 AJAX 를 어떻게 활용할 수 있을지 살펴보자. 첫번째로 사용자가 브라우저의 입력폼에 값을 입력했을때 그 값을 검증하는 기능을 구현해볼 것이다. 이번 예제에서는 간단하게 날짜를 입력했을때, 그 값을 맞게 입력했는지 검증하는 로직을 AJAX 로 적용해 볼것이다. 여러 프로젝트를 수행한 결과 이런 폼 입력값 검증작업의 경우 간단한 것은 자바스크립트를 사용하면 아주 쉽게 끝나버린다. 오히려 서버의 로직을 거치지 않으므로 서버에 부담도 없다. 하지만 서버의 DB 나 혹은 XML 에 담겨진 데이타와 비교 혹은 검증을해야만 하는 경우엔 어쩔 수 없이 서버의 비지니스 로직을 거쳐야 한다. 하지만 이럴경우 코아 비지니스 로직을 수행하기 전에 입력폼의 값을 검증하는 로직을 추가로 코딩해야만 한다. 또한 입력값이 잘못 되었다면 어떤입력값이 어떻게 잘 못되었다고 친절하게 알려주는 새로운 페이지로 이동해야 한다거나, 쉽게는 팝업처리를 할 수도 있다. 이렇게 되면 사용자는 또다시 처음부터 다시 입력을 해야하는 고생을 해야 한다.


이럴경우 AJAX 를 이용한 폼 입력값 검증작업을 해주면 비지니스 로직을 개발할때는 코어 로직만 작성하면 되고, 고객 입장에서는 입력한 값이 어디가 어떻게 잘못된건지 바로 알수 있기때문에 아주 유용한 방법이 될 것이다.


그럼 소스를 살펴보자. 코드가 길어지는 것을 방지하기 위해 되도록 소스는 단순하게 짜여졌다.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
    <title>Using Ajax for validation</title>
 
    <script type="text/javascript">
        var xmlHttp;

        function createXMLHttpRequest() {
            if (window.ActiveXObject) {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            else if (window.XMLHttpRequest) {
                xmlHttp = new XMLHttpRequest();               
            }
        }

        function validate() {
            createXMLHttpRequest();
            var date = document.getElementById("birthDate");
            var url = "ValidationServlet?birthDate=" + escape(date.value);
            xmlHttp.open("GET", url, true);
            xmlHttp.onreadystatechange = callback;
            xmlHttp.send(null);
        }

        function callback() {
            if (xmlHttp.readyState == 4) {
                if (xmlHttp.status == 200) {
                    //var mes = xmlHttp.responseXML.getElementsByTagName("message")[0].firstChild.data;
     var mes = xmlHttp.responseXML.getElementsByTagName("message")[0].firstChild.data;
                    var val = xmlHttp.responseXML.getElementsByTagName("passed")[0].firstChild.data;
                    setMessage(mes, val);
                }
            }
        }
       
        function setMessage(message, isValid) {           
            var messageArea = document.getElementById("dateMessage");
            var fontColor = "red";
           
            if (isValid == "true") {
                fontColor = "green";               
            }
            messageArea.innerHTML = "<font color=" + fontColor + ">" + message + " </font>";
        }

    </script>
  </head>
  <body>
    <h1>Ajax Validation Example</h1>
    Birth date: <input type="text" size="10" id="birthDate" onchange="validate();"/>
    <div id="dateMessage"></div>
  </body>
</html>


<validateion.html 전체 소스 코드>



package ajaxbook.chap4;

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import javax.servlet.*;
import javax.servlet.http.*;

public class ValidationServlet extends HttpServlet {   
   
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        PrintWriter out = response.getWriter();
       
        boolean passed = validateDate(request.getParameter("birthDate"));
        response.setContentType("text/xml");
        response.setHeader("Cache-Control", "no-cache");
        String message = "You have entered an invalid date.";
       
        if (passed) {
            message = "You have entered a valid date.";
        }
        out.println("<response>");
        out.println("<passed>" + Boolean.toString(passed) + "</passed>");
        out.println("<message>" + message + "</message>");
        out.println("</response>");
        out.close();
     }
   
    /**
     * Checks to see whether the argument is a valid date.
     * A null date is considered invalid. This method
     * used the default data formatter and lenient
     * parsing.
     *
     * @param date a String representing the date to check
     * @return message a String represnting the outcome of the check
     */
    private boolean validateDate(String date) {
       
        boolean isValid = true;
        if(date != null) {
            SimpleDateFormat formatter= new SimpleDateFormat("MM/dd/yyyy");
            try {
                formatter.parse(date);
            } catch (ParseException pe) {
                System.out.println(pe.toString());
                isValid = false;
            }
        } else {
            isValid = false;
        }
        return isValid;
    }
}

<ValidationServlet.java 의 전체 소스 코드>



이번 예제는 사실 설명이 불필요할 정도로 매우 간단하다. 브라우저에서는 날짜를 입력받는 필드만 있다. 날짜를 입력하고 난후 포커스가 빠져나가면 입력했던 날짜는 서버 프로그램으로 전송되고 입력된 값이 날짜 형식에 적절한지 판단해서 다시 브라우저로 보내주는 형식이다.

사용자 삽입 이미지

<날짜를 잘못 입력했을 경우>


사용자 삽입 이미지

<날짜 형식으로 입력했을 경우>

Posted by 캠퍼스친구
홈페이지 관련/ajax2007. 1. 26. 23:25
=============================================================================
====== Ajax 관련 자주 방문해야 하는 싸이트 ========
=============================================================================
 
 
 
 
 
 
 
 
 
 
 
=============================================================================
====================== Ajax 관련 오픈소스=======================
=============================================================================
=============================================================================
=============== Ajax 관련 라이브러리 & 툴 =================
=============================================================================
=> FAT(Fade Anything Technique)에 대한 글
 
http://www-128.ibm.com/developerworks/kr/library/os-ecl-ajax/ => Eclipse의 Ajax Toolkit Framework에서 지원되는 툴 (한글)
 
http://www.youngpup.net/2001/domdrag/ => dom dram 관련 싸이트
 
IBM AJAX Toolkit Framework =>IBM AJAX Toolkit Framework
An Eclipse Incubation Project Proposal =>IBM AJAX Toolkit Framework을 이해하는데 도움을 주는 제안서
 
http://httpunit.sourceforge.net/ => HttpUnit 테스팅 프레임웍
 
http://fitnesse.org/ => FitNesse 테스팅 프레임웍
 
http://jwebunit.sourceforge.net/ => 웹 테스팅 프레임웍으로 자바 개바자라면 추천해 본다.

http://www.edwardh.com/jsunit/ => JsUnit 홈페이지


http://devedge-temp.mozilla.org/toolbox/examples/2003/inheritFrom/index_en.html => 넷스케이프 커뮤니케이션의 Bob Clay 는 부모 클래스의 메소드를 자식 클래스에 복사할 수 있는 아주 간단한 메소드를 소개하였다.


http://chrispederick.com/work/webdeveloper/ => Web Developer Extension for FireFox 으로써 파이어폭스 브라우저가 제공해 주는 다양한 기능의 툴바를 다운/설치할 수 있는 싸이트이다.


http://hometown.aol.de/_ht_a/memtronic/ => 자바스크립트 파일을 압축하거나 Obfuscation(자신의 소스코드를 다른 사람이 악의적으로 도용하고나 훔쳐가는 것을 막기 위해서 멤버나 메소드 이름을 의미없는 문자들로 바꾸는 기법)하는 Freeware 싸이트이나 현재버젼에서는 아직까지 자바스크립트에 대한 Obfuscation 은 지원하지 않고 있다.


http://www.jslint.com/ => 자바스크립트 소스코드를 검증해 주는 싸이트


http://www.mozilla.com/ => 모질라 닷컴/파이어 폭스 최신버젼 다운로드


https://addons.mozilla.org/ => FireFox add on home page


https://addons.mozilla.org/extensions/?application=firefox => firefox add on extensions


http://www.activeperl.com/ => 펄의 런타임 환경인 ActivePerl 을 다운로드 받을 수 있다.


http://jsdoc.sourceforge.net/ => javadoc 명령으로 HTML API를 생성하듯이 자바스크립트의 주석을 바탕으로 HTML 다큐먼트를 생성하는 오픈소스


http://www.openqa.org/selenium/ => html 및 자바스크립트를 검사해주는 아주 훌륭한 오픈소스다. 실험적인 프로그램이지만 100점 주고 싶다.


http://www.activeperl.com/ => 펄 런타임 환경 다운로드 싸이트


http://jsdoc.sourceforge.net/ => jsDoc


http://www.json.org => JSON 홈페이지


http://www.ashleyit.com/rs/main.htm => Remote Scripting 관련하여 Brent Ashley 가 운영하는 싸이트




=============================================================================

======================= Ajax 응용 싸이트 =========================
=============================================================================
 
 
=> 구굴에서 만든 달력 및 메모
 
http://maps.a9.com/ => Ajax 를 이용한 map 싸이트
 

http://maps.google.com/ => 구굴 맵


http://www.google.com/ig => Draggable DOM pattern 을 아주 훌륭하게 적용한 싸이트


http://www.google.com/webhp?complete=1&hl=kor => 구굴 Suggest 한글 검색창


http://www.google.com/webhp?complete=1&hl=en => 구굴 Suggest 영문 검색창


http://www.netflix.com/BrowseSelection => ajax 를 이용한 툴팁을 구현한 싸이트


http://www.apple.com/itunes/ => AJAX 관련 refresh 기능을 구현해 놓은 싸이트.(애플 itunes 뮤직 다운로드 자동 카운트)


http://www.digg.com/spy => AJAX 관련 refresh 기능을 구현해 놓은 싸이트.(새로운 정보 컨텐츠 리스트 자동 소팅 기능)

 
 
 

=============================================================================

============== Ajax 관련  기타  참고 싸이트 ==============
=============================================================================

http://www.apress.com/book/supplementDownload.html?bID=10042&sID=3021 => Foundation of Ajax 소스 다운로드 url

Posted by 캠퍼스친구