행복한 아빠

[Grails1.0 사용자 가이드] 6. 웹 계층 - 7 본문

Grails

[Grails1.0 사용자 가이드] 6. 웹 계층 - 7

행복한아빠 2008. 3. 10. 20:41


6. 웹 계층 - 7

  • Ajax
  • 컨텐트 종류 결정(Content Negotiation)


6.7 Ajax
Ajax는 javascript와 XML의 비동기 처리를 위한 것이다. 이것은 점점 풍부한(richer) 웹 애플리케이션의 변화를 주도하고 있다. 이런 종류의 애플리케이션은 보통 RubyGroovy 언어와 같이 민첩하고(agile) 동적인 프레임워크가 어울린다. Grails는 Ajax 태그 라이브러리를 통해 Ajax 애플리케이션 작성을 지원한다. Ajax 태그의 전체 목록은 태그 라이브러리 레퍼런스를 보라.

6.7.1 Prototype을 이용한 Ajax
기본으로 Grails는 Prototype 라이브러리를 이용한다. 그러나 플러그인 시스템을 통해 Dojo, Yahoo UI 그리고 Google Web Tookkit과 같은 다른 프레임워크를 지원한다.

이장에서는 Grails의 Prototype 지원을 다룬다. 시작하기 위해서 페이지의 <head> 태그에 다음 줄을 추가해야 한다.

<g:javascript library="prototype" />

이것은 Prototype을 위한 올바른 참조를 자동으로 넣기 위해 javascript 태그를 사용했다. 만일 Scriptaculous가 필요하다면 다음과 같이 대체할 수 있다.

<g:javascript library="scriptaculous" />


6.7.1.1 원격 링크하기(Linking)
원격에 있는 컨텐트를 여러 방법으로 로드할 수 있다. remoteLink 태그를 사용하는 것이 가장 일반적인 방법이다. 이 태그는 비동기 요청을 실행하는 HTML anchor 태그를 생성하고 원할 경우 특정 엘리먼트안에 요청에 대한 응답을 넣을 수 있다. 가장 간단한 생성 방법은 다음과 같다.

<g:remoteLink action="delete" id="1">Delete Book</g:remoteLink>

위 링크는 현재 컨트롤러의 delete 액션에 id를 1로 해서 비동기 요청을 보낸다.

6.7.1.2 내용 업데이트
이것으로도 대단하지만 보통 무슨 일이 일어났는지 사용자에게 어떤 피드백이라도 제공하길 원할 것이다.

def delete = {
    def b = Book.get( params.id )
    b.delete()
    render "Book ${b.id} was deleted"
}

GSP 코드:

<div id="message"></div>
<g:remoteLink action="delete" id="1" update="message">Delete Book</g:remoteLink>

위 예제는 액션을 실행하고 이 경우 "Book 1 was deleted"라는 응답을 message div 태그 내용에 쓸 것이다. 태그의 update 애트리뷰트에 의해 이것이 이루어진다. 이 속성은 또한 실패 시 어떤 것이 업데이트될지 결정하는 맵을 가질 수 있다.

<div id="message"></div>
<div id="error"></div>
<g:remoteLink action="delete" id="1"
    update="[success:'message',failure:'error']">Delete Book</g:remoteLink>

요청이 실패하면 여기 error div를 업데이트할 것이다.

6.7.1.3 원격 폼 submit하기
다음 두 가지 방법 중 하나로 HTML 폼도 비동기로 submit할 수 있다. 첫번째로 remoteLink 태그와 유사한 애트리뷰트들을 가지는 formRemote 태그를 사용하는 것이다.

<g:formRemote url="[controller:'book',action='delete']" update="[success:'message',failure='error']">
    <input type="hidden" name="id" value="1" />
    <input type="submit" value="Delete Book!" />
</g:formRemote>

또는 다른 방안으로 submit 버튼을 만들기 위해 submitToRemote 태그를 사용할 수 있다. 이것은 원격으로 submit하는 버튼을 만드는데 액션에 영향 받지 않고 동작한다.

<form action="delete">
    <input type="hidden" name="id" value="1" />
    <g:submitToRemote action="delete" update="[success:'messsage',failure:'error']" />
</form>


6.7.1.4 Ajax 이벤트
어떤 이벤트가 발생하면 특정 자바스크립트를 호출한다.  모든 이벤트는 "on" 접두어로 시작하고 사용자에게 적당한 피드백을 주거나 다른 액션을 실행할 수 있다.

<g:remoteLink action="show"
    id="1"
    udpate="success"
    onLoading="showProgress()"
    onComplete="hideProgress()">Show Book 1</g:remoteLink>

위 예제는 진행바를 보여주기 위해 "showProgress()" 함수나 그 외 적당한 함수를 실행할 것이다. 다른 이벤트에는 아래와 같은 것이 있다.

  • onSuccess: 성공했을 때 호출되는 자바 스크립트 함수
  • onFailure: 호출이 실패했을 때 호출되는 자바 스크립트 함수
  • on_ERROR_CODE: 특정 에러 코드를 처리하기 위해 호출되는 자바스크립트 함수 (예: on404="alert('not found!')")
  • onUninitialized: Ajax 엔진 초기화가 실패했을 때 호출되는 자바스크립트 함수
  • onLoading: 원격 함수가 응답을 로딩할 때 호출되는 자바스크립트 함수
  • onLoaded: 원격 함수가 응답 로딩을 완료했을 때 호출되는 자바스크립트 함수
  • onComplete: 모든 업데이트를 호함하여 원격 함수가 완료되었을 때 호출되는 자바스크립트 함수

만일 XmlHttpRequest 객체의 참조가 필요할 경우 이것을 얻기 위해 암묵적인 이벤트 파라메터 e를 사용할 수 있다.

<g:javascript>
function fireMe(e) {
    alert("XmlHttpRequest = " + e)
}
</g:javascript>
<g:remoteLink action="example"
    update="success"
    onSuccess="fireMe(e)">Ajax Link</g:remoteLink>


6.7.2 Dojo를 이용한 Ajax
Grails에 Dojo를 지원하기 위한 외부 플러그인 기능이 있다. 플러그인을 설치하기 위해서는 명령 프롬프트(terminal window)의 프로젝트 루트에서 다음 명령어를 입력한다.

grails intall-plugin dojo

이 명령어는 Dojo의 현재 지원되는 버전을 다운로드하고 당신의 Grails 프로젝트에 설치할 것이다. 이것으로 다음의 참조를 페이지 맨 위에 추가할 수 있다.

<g:javascript library="dojo" />

이제 remoteLink, formRemote 그리고 submitToRemote와 같은 Grails 태그들이 Dojo 리모팅(remoting)으로 작동한다.


6.7.3 GWT를 이용한 Ajax
Grails는 또한 플로그인을 통해 Google Web Toolkit을 지원하고 포괄적인 문서는 Grails wiki에서 찾아볼 수 있다.

6.7.4 서버에서의 Ajax
Ajax의 X가 XML을 위한 것이기는 하지만 Ajax을 구현하기 위한 여러가지 다른 방법이 있는데 전형적으로 다음과같이 나눌 수 있다.

  • 컨텐트 중심 Ajax: 페이지를 업데이터 하기 위해 원격 호출 결과로 단지 HTML을 사용한다.
  • 데이터 중심 Ajax: 실제로 서버에서 XML이나 JSON 응답을 클라이언트로 보내고 프로그램으로 페이지를 업데이트한다.
  • 스크립트 중심 Ajax: 실시간에 실행하기 위해 서버는 자바스크립트 스트림을 보낸다.

Ajax 장의 대부분의 예제들은 페이지 업데이트를 위해 컨텐트 중심의 Ajax을 다룬다. 그러나 어쩌면 당신은 데이터 중심이나 스크립트 중심의 Ajax을 사용하고 싶을지도 모른다. 여기에서는 다른 스타일의 Ajax를 다루고 있다.

컨텐트 중심 Ajax
컨텐트 중심 Ajax는 서버에서 HTML 일부를 반환하면 보통 render 메소드로 템플릿을 렌더링함으로써 이루어진다.

def showBook = {
    def b = Book.get(params.id)
    render(template:"bookTemplate", model:[book:b])
}

이것을 호출하는 클라이언트는 remoteLink 태그를 사용한다.

<g:remoteLink action="showBook" id="${book.id}" update="book${book.id}">Update Book</g:remoteLink>
<div id="book${book.id}">
    <!-- existing book mark-up -->
</div>


JSON을 이용한 데이터 중심 Ajax
데이터 중심의 Aajx는 클라이언트에서 응답을 처리(evaluating)하고 업데이트하는 프로그램을 작성한다. 보통 Grails의 JSON 마셀링(marshaling) 기능을 이용하여 JSON 응답을 만들 수 있다.

import grails.converters.*
def showBook = {
    def b = Book.get(params.id)
    render b as JSON
}

그런 다음 클라인언트는 받은 JSON 응답을 Ajax 이벤트 핸들러로 파싱(parsing)한다.

<g:javascript>
function updatebook(e) {
    var book = eval("(" + e.responseText + ")")  // evaluate the JSON
    $("book"+book.id+"_title").innerHTML = book.title
}
</g:javascript>
<g:remoteLink action="test" update="foo" onSuccess="updateBook(e)">Update Book</g:remoteLink>
<g:set var="bookId">book${book.id}</g:set>
<div id="${bookId}">
    <div id="${bookId}_title">The Stand</div>
</div>


XML을 이용한 데이터 중심 Ajax
XML을 이용한 서버 사이드는 동일하다.

import grails.converters.*
def showBook = {
    def b = Book.get(params.id)
    render b as XML
}

하지만 클라이언트에서는 DOM을 사용하기에 좀 더 복잡하다.

<g:javascript>
function updateBook(e) {
    var xml = e.responseXML
    var id = xml.getElementsByTagName("book").getAttribute("id")
    $("book"+id+"_title")=xml.getElementsByTagName("title")[0].textContent
}
<g:javascript>
<g:remoteLink action="test" update="foo" onSuccess="updateBook(e)">Update Book</g:remoteLink>
<g:set var="bookId">book${book.id}</g:set>
<div id="${bookId}">
    <div id="${bookId}_title">The Stand</div>
</div>


자바스크립트를 이용한 스크립트 중심 Ajax
스크립트 중심 Ajax는 실제 자바스크립트를 돌려주고 클라이언트에서 그것을 실행(evaluate-평가)한다. 아래에 이러한 예제가 있다.

def showBook = {
    def b = Book.get(params.id)
    response.contentType = "text/javascript"
    String title = b.title.encodeAsJavascript()
    render "$('book${b.id}_title')='${title}'"
}

염두해야 할 중점은 contentType을 text/javascript로 설정하는 것이다. 만일 클라이언트에서 Prototype을 사용하고 있고 contentType 설정이 이 같이 되면 반환되는 자바스크립트는 자동으로 평가(실행)된다.

분명 이 경우 클라이언트 쪽의 API가 변경되면 서버가 제대로 작동되지 않은 심각한 문제가 있다. 이 이유 때문에 Rails는 RJS같은 것을 가지고 있다. 하지만 Grails는 현재 RJS같은 기능을 제공하지 않고 비슷한 기능을 제공하는 Dynamic JavaScript Plug-in이 있다.


6.8 컨텐트 종류 결정(Content Negotiation)
Grails는 HTTP Accept 헤더나 명시적인 format 파라메터 또는 URI 확장자를 이용한 컨텐트 종류 결정(Content negotiation) 기능을 내장하고 있다.

Mime types 설정
Content negotiation을 다루기 전 Grails에게 어떤 종류의 컨텐트를 지원할 것인지 알려줘야 한다. 기본으로 grails-app/conf/Config.groovygrails.mime.types를 이용하여 다른 종류의 컨텐트 종류들을 설정할 수 있다.

grails.mime.types = [ xml: ['text/xml', 'application/xml'],
                            text: 'text-plain',
                            js: 'text/javascript',
                            rss: 'application/rss+xml',
                            atom: 'application/atom+xml',
                            css: 'text/css',
                            cvs: 'text/csv',
                            all: '*/*',
                            json: 'text/json',
                            html: ['text/html','application/xhtml+xml']
                          ]

위의 설정에서 Grails 요청의 포맷이 'text/xml'이나 'application/xml' 미디어 종류를 가지고 있을 때 간단히 'xml'로 결정한다는 것을 뜻한다. 원하는 고유의 타입을 맵에 새로운 엔트리로 추가할 수 있다.

Accept 헤더를 이용한 컨텐트 종류 결정
모든 HTTP 요청은 클라이언트가 어떤 미디어 종류(또는 mime types)를 수용하는지 정의하는 특별한 Aceept 헤더를 가지고 있다. 예전 브라우저는 보통 다음과 같다.

*/*

이 의미는 단순히 모든 것을 뜻한다. 그러나 최신 브라우저는 다음과 같이 좀 더 유용한 내용을 보낸다(FireFox Accept 헤더 예)

text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5

Grails는 이 포맷을 파싱하여 request 객체에 속성에 추가한다. 위 예제는 다음의 확인(assertion) 코드를 통과시켜준다.

assert 'html' == request.format

어째서 일까? text/html 미디어 타입은 0.9의 제일 높은 자격 등급(quality rating)을 가지고 있기 때문이다. 만일 전에 언급한 예전 브라우저 같은 경우는 그 결과가 좀 다를 것이다.

assert 'all' == request.format

이 경우 클라인언트는 '모든' 가능한 포맷을 받아들인다. 컨트롤러에서 서로 다른 종류의 요청 포맷을 다루기 위해서는 마치 switch 문장 비슷한 withFormat 메소드를 사용할 수 있다.

import grails.converters.*class BookController {
    def books
    def list = {
        this.books = Book.list()
        withFormat {
            html bookList:books
            js { render "alert('hello')" }
            xml { render books as XML }
        }
    }
}

만일 우선 순위가 높으 포맷이 html이면 Grails는 오직 html() 호출만을 실행할 것이다. 이 결과 Grails는 grails-app/views/book/list.html.gspgrails-app/views/book/list.gsp 둘 중 하나의 뷰를 찾을 것이다. 만일 포맷이 xml이면 XML 응답을 렌더링하기 위해 xml 의 클로우저를 실행할 것이다.

예전 브라우저 처럼 accept 헤더가 'all'일 경우 withinFormat 메소드안의 호출 순서에 의해 포맷을 결정한다. 위 예제에서는 이 경우 html 메소드를 먼저 실행할 것이다.

사용자 삽입 이미지
withFormat을 사용할 때 컨트롤러 액션에서 마지막 호출이 되도록 한다. 다음에 어떻게 해야하는지 withFormat 메소드의 반환값을 사용하여 액션이 결정한다.


format 파라메터를 이용한 컨텐트 종류 결정
요청 헤더를 무시하고 사용하지 않을 때는 특별한 format 파라메터를 사용하여 포맷을 재정의할 수 있다.

/book/list?format=xml

물론 URLMapping 정의에서 이 파라메터를 정의할 수도 있다.

"/book/list"(controller:"book", action:"list") {
    format = "xml"
}


URI 확장자를 이용한 컨텐트 종류 결정
Grails는 또한 URI 확장자를 통해 컨텐트 종류를 결정하느 것을 지원한다. 다음 URI를 예를 들어보자.

/book/list.xml

Grails는 확장자를 제거하고 이것을 대신 /book/list로 매핑한다. 동시에 확장자를 기반으로 컨텐트 포맷을 xml로 설정한다. 이런 기능은 기본으로 사용이 가능하므로 이 기능을 끄고 싶다면 grails-app/conf/Config.groobygrails.mime.file.extensions 속성을 false로 설정한다.

grails.mime.file.extensions = false


컨텐트 결정 테스트
통합 테스트(테스팅 장을 보라)에서 컨텐트 결정을 테스트하기 위해서는 다음과 같이 들어오는 요청의 헤더를 조작하거나,

void testJavascriptOutput() {
    def controller = new TestController()
    controller.request.addHeader "Accept", "text/javascript, text/html, application/xml, text/xml, */*"
    controller.testAction()
    assertEquals "alert('hello')", controller.response.contentAsString
}

또는 동일한 효과를 내기 위해 format 파라메터를 설정할 수 있다.

void testJavascriptOutput() {
    def controller = new TestController()
    controller.params.format = 'js'
    controller.testAction()
    assertEquals "alert('hello')", controller.response.contentAsString
}

---
원문: 6.7 Ajax
12 Comments
댓글쓰기 폼