Skip to content
This repository was archived by the owner on Jan 7, 2021. It is now read-only.

Ajax on rails.textile

hschoimd edited this page Jun 5, 2011 · 22 revisions

AJAX on Rails

이 안내서는 레일스에 내장되어 있는 Ajax/JavsScript 기능성을 다루고 있어서 다양하고 동적인 AJAX 어플리케이션을 쉽게 만들수 있게 해 줄 것입니다. 여기서 다룰 주제는 다음과 같습니다.

  • AJAX와 관련된 기술들에 대한 간략한 소개
  • 자바스크립트를 레일스방식으로 다루기: Rails helpers, RJS, Prototype and script.aculo.us
  • 자바스크립트 기능 테스트하기

안녕, AJAX – 간략한 소개

‘show me the code’형의 사람이라면 이 부분을 건너띠고 RJS 부분으로 이동하고 싶어 할 수 있습니다. 그러나, 이 부분을 읽어 볼 것을 정말로 권하는데, 레일스에서 Ajax를 잘 이해하려면 여기서 언급되는 DOM의 기본개념, http 요청하는 방법, 그리고 기타 다른 토픽들이 필요할 것이기 때문입니다.

AJAX

(= A synchronous Ja vaScript + X ML)

Ajax의 기본 용어설명, 웹어플리케이션을 만드는 새로운 방식

DOM 객체

DOM 의 기본개념, DOM 이 만들어진 방법, DOM 의 속성, 특징, DOMAJAX 의 핵심인 이유

Standard HTML communication vs AJAX

통상적인 요청과 AJAX요청의 차이점, 레일스에서 AJAX 를 이해하는데 있어서의 이러한 차이점의 중요성(다음 섹션에 언급되는 *_remote 헬퍼메소드와 연관됨)

내장 레일스 헬퍼메소드

레일스의 기본 자바스크립트 프레임워크는 Prototype 입니다. Prototype 은 일반적으로 사용할 수 있는 자바스크립트 프레임워크로서 DOM 객체 조작과 AJAX 를 제공하며, 그외에도 유틸리티 함수로부터 객체지향적 구조에 이르는 자바스크립트 기능을 제공해 줌으로써 동적인 웹어프리케이션을 쉽게 개발할 수 있도록 하기 위해 만들어 졌습니다. Prototype 은 특정언어를 위해서 만들어진 것이 아니기 때문에, 이와 관련된 다수의 헬퍼메소드를 레일스가 제공해서 Prototype이 레일스 뷰 파일들과 매끄럽게 통합될 수 있도록 해줍니다.

이러한 헬퍼메소드에 접근하기 위해서 해야 할 것은 대개는 application.html.erb와 같은 웹어플리케이션의 최상위 레이아웃 파일에 Prototype 프레임워크를 아래와 같이 포함시키는 것입니다.

javascript_include_tag ‘prototype’

이제 레일스 어플리케이션에 AJAX에 대한 사랑을 더해 줄 준비가 된 것입니다.

전형적인 AJAX 레일스 헬퍼메스드: link_to_remote

아마도 가장 많이 사용하는 헬퍼메소드인 link_to_remote 로부터 시작해 봅시다. 관련 도큐먼트에 기록된 것을 볼 대 이 헬퍼메소드는 재미있는 특징을 가지고 있는데, 이 헬퍼메소드로 넘겨지는 옵션들은 모든 AJAX 헬퍼메소드들에서도 공유하게 된다는 것입니다. 따라서 이 헬퍼메소드의 옵션과 작동 기전을 배우게 되면 다른 헬퍼메소드를 사용하는데 도움이 될 것입니다.

link_to_remote 함수는 표준 link_to 헬퍼메소드와 그 특징이 같습니다. :

def link_to_remote(name, options = {}, html_options = nil)

그리고 아래에 link_to_remote 헬퍼메소드에 대한 간단한 예제가 있습니다.

link_to_remote “Add to cart”,
:url => add_to_cart_url(product.id),
:update => “cart”

  • 제일 처음에 있는 매개변수는 문자열인데, 웹페이지에 나타나는 링크의 텍스트입니다.
  • 두번째 매개변수는 options 해쉬이며, AJAX에 관련된 기능을 가질 때 가장 재미있는 부분입니다.
    • :url 이것은 가장 간단한 리모드 링크를 만들기 위해 반드시 필요한 매개변수입니다. 기술적으로는, :url 매개변수 없이 빈 해쉬를 넘길 수 있지만, 이런 경우에는 프로그래머의 의도와는 상관없이 POST 요청시에 사용되는 주소가 현재의 주소가 될 것입니다. 이 URl 주소에는 AJAX 액션 핸들러의 위치를 지정해 줍니다. 대개 이 URL에는 레일스REST 뷰 헬퍼메소드를 이용해서 지정하게 되지만, url_for 형태로 지정할 수도 있습니다.
    • :update 기본적으로 서버로부터의 응답을 웹페이지상에 보여지기 하는데는 2가지 방법이 있습니다. 하나는 RJS에 관련된 사항하고 이것은 다음 장에서 언급할 것입니다. 다른 하나는 업데이트하고자는 하는 요소의 DOM 아이디 값을 지정하는 것입니다. 위의 예제에서는 이것에 대한 가장 간단한 방법을 소개하지만, 서버가 웹페이지에 표시될 에러 메시지로 응답을 할 때 문제가 생기게 됩니다. 그러나 레일스는 이러한 상황에 대한 해결방법을 가지고 있습니다.

link_to_remote “Add to cart”,
:url => add_to_cart_url(product),
:update => { :success => “cart”, :failure => “error” }

서버가 200번으로 응답할 때 위의 예제는 이전의 간단한 예제와 같은 결과를 보여 줄 것입니다. 그러나, 에러가 발생할 경우에는 “cart” 요소가 아니라 “error” 아이디를 가지는 DOM 요소가 업데이트 됩니다.

  • position : 기본적으로(이전 예제와 같이 이 옵션을 명시하지 않은 때), 서버로부터의 응답은 명시된 DOM 아이디를 가진 요소에 표시되어 이전 값이 있을 경우 이를 대체하게 됩니다. 경우에 따라 해당 요소의 원래의 값을 변경하기 않도록 이러한 동작을 변경하기를 원할 수 있습니다. 여기서 문제는 새로운 값을 어디에 둘 것이냐 입니다. 바로 이때 position 매개변수를 지정해서 위치를 지정할 수 있으며, 4가지 옵션을 가질 수 있습니다.
    • :before : 서버응답 내용을 대상 요소 직전에 삽입합니다. 더 정확하게 말하면, 서버응답으로부터 텍스트 노드를 만들어서 대상 요소의 직전 형제노드로 삽입합니다.
    • :after : :before 와 비슷한 동작을 합니다만, 이 경우에는 대상 요소 이후에 서버응답이 삽입됩니다.
    • :top : 텍스트를 원본 내용 이전에 있는 대상 요소로 삽입합니다. 대상 요소가 비어 있는 경우에는 :position 값을 전혀 설정하지 않은 상태와 동일하게 됩니다.
    • :bottom : :top 의 반대 옵션으로 서버응답이 대상 요소의 원본 내용 다음에 삽입됩니다.

:bottom 옵션을 이용하는 전형적인 예는 기존 목록에 <li> 요소를 새로 추가하는 것입니다.

link_to_remote “Add new item”,
:url => items_url,
:update => ‘item_list’,
:position => :bottom

  • :method : 뷰 템블릿에 원격 링크를 추가할 대 가장 기본적으로 POST 요청을 하게 되어 이것이 디폴트 동작이 되는 것입니다. 그러나, 경우에 따라 특정 내용을 업데이트(PUT)하거나 삭제(DELETE)하고자 할 때 :method 옵션을 이용하여 명시할 수 있습니다. 특정 목록에서 하나의 항목을 삭제하기 위한 전형적인 AJAX 링크에 대한 예를 보겠습니다.

link_to_remote “Delete the item”,
:url => item_url(item),
:method => :delete

주의할 것은, 디폴트 동작(POST)을 변경하지 않을 경우 위의 코드는 삭제보다는 생성(create) 동작으로 작동하게 될 것입니다.

  • JavaScript filters : 자바스크립트 코드로 감싸게 되면 원격 호출을 변경할 수 있습니다. 예를 들어 이전 예제에서, 특정 링크를 없애고자 할 때, 유저에게 간단한 텍스트 박스를 보여주어 확인을 요구할 수 있습니다. 이것은 전형적인 예로서 이 옵션을 이용하여 수행할 수 있습니다. 하나씩 알아보도록 하겠습니다.
    • :confirm => msg : 자바스크립트 확인 대화창을 띄워 msg 를 보여줍니다. 유저가 ’OK’를 선택하면 요청 작업이 진행되고 그렇지 않으면 취소됩니다.
    • :condition => code : 논리값을 반환하는 code 를 실해하여 참이면 작업을 진행하고 그렇지 않으면 취소합니다.
    • :before => code : 요청을 진행하기 직전에 code 를 실행합니다. 해당 코드의 결과는 호출에 영향을 미치지 않습니다. 진행상황을 표시하기 위해서 흔히 사용되며 다음 예제에서 이러한 동작을 알 수 있습니다.
    • :after => code : 요청을 실행한 후에 code 를 평가하게 됩니다. 다음 섹션에서 다루게 되겠지만 이것은 :success:complet 콜백과는 다릅니다. 이것들은 요청이 완료된 후에 실행되지만, :after 로 넘겨지는 코드는 원격 호출 된 직후에 실행된다는 것에 주목해야 합니다. 일반적인 예를 들면 웹페이지의 요소를 사용하지 못하게 하거나 요청이 완료되는 시점에서 더이상 동작을 하지 못하도록 할 경우입니다.
    • :submit => dom_id : 이 옵션은 link_to_remote 헬퍼메소드에 해당되지 않지만 모든 것을 언급한다는 의미에서 여기서 다루기로 하겠습니다. 디폴트로, 유저가 서밋하는 폼 요소의 상위 요소는 현재의 폼이 됩니다. 이 때 디폴트 동작을 변경하고자 할 때 이 옵션을 사용하면 됩니다. 이 옵션을 지정하게 되면, DOM 아이디값 dom_id 을 지정해서 상위 요소를 변경할 수 있게 됩니다.
    • :with > code : code 값 안에 할당된 자바스크립트 코드가 평가된 후 요청된 URL에 매개변수(들)로 추가됩니다. 그러므로 code 값은 유효한 URL 쿼리문자열을 반환하게 됩니다(like “item_type=8” or “item_type=8&sort=true”). 대개는 웹페이시에서 값들을 얻게 됩니다. 자 예제를 보겠습니다.

link_to_remote “Update record”,
:url => record_url(record),
:method => :put,
:with => “‘status=’ + ‘encodeURIComponent($(’status’).value) + ‘&amp;completed=’ + $(‘completed’)”

이 코드는 웹페이지내의 ’status’와 ‘compeleted’ DOM 아이디를 가지는 요소로부터 가져 온 2개의 매개변수를 레일스가 만들어 주는 표준 URL에 추가하여 원격 링크를 만들어 줍니다.

  • Callbacks : AJAX 호출은 보통 비동기화 되어 일어나지만, 그 이름이 암시하는 바와 같이 (반드시 그런 것은 아니지만, 마지막 옵션인 :type 을 설정하여 요청을 동기화할 수 있습니다) 특정 요청이 시작된 후 해당 요청과 통신할 수 있는 유일한 방법은 콜백을 지정하는 것입니다. 재량에 따라 6개의 옵션을 있을 수 있습니다(모드 가능성 있는 응답 형태를 계산해 보면 508개나 되지만 언급한 6개만이 가장 자주 사용되어 상수로 정의하게 되었습니다).
    • :loading: => code : 요청이 데이타를 받는 중에 있지만 완료되지 않은 상태입니다.
    • :loaded: => code : 요청으로부터 데이타 이동이 완료되었지만 아직 처리되지 못하여 반환되지 않은 상태입니다.
    • :interactive: => code : :loaded 직후 단계로서 데이타 이동이 완료되어 처리된 상태입니다.
    • :success: => code : 서버로 데이타 이동이 완료되어 처리된 후에 “200 OK” 응답을 보낸 상태입니다.
    • :failure: => code : 서버로 데이타 이동이 완료되어 치리된 후에 “200 OK” 외의 값으로 응답을 보낸 상태입니다.(대개는 404 또는 500 값이지만 일반적으로 100에서 509까지의 상태 코드값을 가질 수 있습니다)
    • :complete: => code : 이전 두개의 옵션을 합한 상태입니다. 즉, 요청이 완료되어 데이타가 분석되고 상태 코드값을 반환한 상태입니다(상태 코드값은 어떠한 값이어도 됩니다).
    • 기타 다른 상태 코드값(100-509) : 404 와 같은 기타 HTTP 상태 코드값을 점검할 경우에는 상태 코드값을 숫자로 사용하면 됩니다.

      link_to_remote “Add new item”,
      :url => items_url,
      :update => “item_list”,
      404 => “alert(‘Item not found!’)”

      가장 자주 사용하는 콜백인 :success, :failure, :complete 가 동작하는 전형적인 예제를 봅시다.

      link_to_remote “Add new item”,
      :url => items_url,
      :update => “item_list”,
      :before => “$(‘progress’).show()”,
      :complete => “$(‘progress’).hide()”,
      :success => “display_item_added(request)”,
      :failure => “display_error(request)”
  • :type : 어떤 분명치 않은 이유로 해서 요청을 동기화하고 싶을 때(요청이 처리되고 상태 값을 반환하지 않은 상태에서 브라우져를 사용할 수 없게 해야 하는 경우) 이 옵션을 :synchronous 로 설정해서 이용할 수 있습니다.
  • 마지막으로 html_options 매개변수를 이용해서, 최종적으로 만들어지는 태그에 HTML 속성을 추가할 수 있습니다. 이것은 link_to 헬퍼메소드의 동일한 매개변수와 같이 동작을 합니다. 그러나 hrefonclick 매개변수에 대해서는 재미있는 부대효과가 있습니다.
    • href 매개변수를 지정할 경우, 클라이언트 브라우져에서 자바스크립트가 작동하지 않더라도 AJAX 링크가 멋지게 깨지면서 링크는 지정한 주소로 연결이 가능하게 됩니다.
    • link_to_remote 는 링크의 onclick 핸들러에 원격 콜을 지정해 두게 되면 AJAX 동작을 하게 됩니다. html_options[:onclick] 을 설정하면 디폴트 동작을 변경하게 되므로 주의를 기울려야 합니다.

이제 link_to_remote 대한 설명을 마칩니다. 하나의 헬퍼함수에 대해서 소화하기에는 많은 양이지만 기억할 것은 이 옵션들은 레일스 뷰 헬퍼메소들에 대해서 일반적으로 적용할 수 있어서 다음 섹션에서 차이점과 추가된 매개변수들에 대해서 알아 보도록 하겠습니다.

AJAX

Prototype 헬퍼메소드를 이용해서 뷰템플릿에 AJAX 폼을 추가하는데는 3가지 방법이 있습니다. 약간씩의 차이점이 있지만 결국은 같은 목적을 위한 것입니다. 즉, 표준 HTTP 요청/응답 주기를 이용해서 서밋(submit)하는 대신에, 웹페이지를 다시 로딩하지 않고 비동기적으로 서밋하게 됩니다. 이러한 메소드는 다음과 같습니다.

  • remote_form_for (and it’s alias form_remote_for) 는 매개변수로서 하나의 리소스, 즉, 모델이나 리소스배열(중첩리소스)을 사용하기 때문에 3가지 방법 중에 가장 밀접하게 레일스에 연결됩니다.
  • form_remote_tag 는 백그라운드에서 폼 데이터를 시리얼로 보냄으로서써 폼을 AJAX화 합니다.
  • submit_to_remotebutton_to_remote 는 이전 두개의 방법보다는 더 자주 사용되지는 않습니다. AJAX 폼을 만들기보다는 버튼이나 input 컴포넌트를 추가해 줍니다.

작동되는 것은 하나씩 알아 보도록 합시다!

remote_form_for
form_remote_tag
submit_to_remote

Observing Elements

observe_field
observe_form

Calling a Function Periodically

periodically_call_remote

Miscellaneous Functionality

remote_function
update_page

JavaScript the Rails way: RJS

지난 섹션에서, 서버에 몇가지 AJAX 요청을 보내고 ( :update 옵션을 이용해서) 웹페이지에 HTML응답을 삽입한 바 있습니다. 그러나, 때로는 좀 더 복잡한 동작을 웹페이지에 적용해야 할 경우가 필요한데, 바로 이런 경우에 자바스크립트나 RJS를 이용할 수 있습니다. 두 경우 모두에서 서버로 자바스크립트 명령을 보내게 되는데, 전자의 경우에는 바닐라 자바스크립트를, 후자의 경우는 레일스로 코딩을 하고 잠시 레일스가 자바스크립트를 만들 때까지 기다리게 됩니다. 그것은 기본적으로 RJS가 루비 DSL이어서 레일스 코드내에 자바스크립트 코드를 삽입하기 때문입니다.

JavaScript without RJS

먼저 서버로 자바스크립트를 수동으로 보내는 방밥을 알아볼 것입니다. 실제로는 이런 경우는 없겠지만 어떤 상황이 일어나고 있는지를 이애해 두는 것은 재미있는 일입니다.

def javascript_test
render :text => “alert(‘Hello, world!’)”,
:content_type => “text/javascript”
end

(주의: 바로 위의 메소드를 점검하기 위해서는, javascript_test 액션으로 연결되는 :url 옵션을 설정해서 link_to_remote 를 만들어야 합니다.)

위의 메소드에서는 헤더변수인 Content-Type을 지정해서 브라우져가 전송되는 텍스트를 단순한 텍스트로 보지 주지 않고 자바스크립트 디폴트 동작을 하도록 지시하게 됩니다.

Inline RJS

As we said, the purpose of RJS is to write Ruby which is then auto-magically turned into JavaScript by Rails. The above example didn’t look too Ruby-esque so let’s see how to do it the Rails way:

def javascript_test
render :update do |page|
page.alert “Hello from inline RJS
end
end

The above code snippet does exactly the same as the one in the previous section – going about it much more elegantly though. You don’t need to worry about headers,write ugly JavaScript code into a string etc. When the first parameter to render is :update, Rails expects a block with a single parameter (page in our case, which is the traditional naming convention) which is an instance of the JavaScriptGenerator:“http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html” object. As it’s name suggests, JavaScriptGenerator is responsible for generating JavaScript from your Ruby code. You can execute multiple method calls on the page instance – it’s all turned into JavaScript code and sent to the server with the appropriate Content Type, “text/javascript”.

RJS Templates

If you don’t want to clutter your controllers with view code (especially when your inline RJS is more than a few lines), you can move your RJS code to a template file. RJS templates should go to the /app/views/ directory, just as .html.erb or any other view files of the appropriate controller, conventionally named js.rjs.

To rewrite the above example, you can leave the body of the action empty, and create a RJS template named javascript_test.js.rjs, containing the following line:

page.alert “Hello from inline RJS

RJS Reference

In this section we’ll go through the methods RJS offers.

JavaScriptGenerator Methods
DOM Element Manipulation

It is possible to manipulate multiple elements at once through the page JavaScriptGenerator instance. Let’s see this in action:

page.show :div_one, :div_two
page.hide :div_one
page.remove :div_one, :div_two, :div_three
page.toggle :other_div

The above methods (show, hide, remove, toggle) have the same semantics as the Prototype methods of the same name. You can pass an arbitrary number (but at least one) of DOM ids to these calls.

Inserting and Replacing Content

You can insert content into an element on the page with the insert_html method:

page.insert_html :top, :result, ‘42’

The first parameter is the position of the new content relative to the element specified by the second parameter, a DOM id.

Position can be one of these four values:

  • :before Inserts the response text just before the target element.
  • :after The response is inserted after the target element.
  • :top Inserts the text into the target element, before it’s original content.
  • :bottom The counterpart of :top: the response is inserted after the target element’s original content.

The third parameter can either be a string, or a hash of options to be passed to ActionView::Base#render – for example:

page.insert_html :top, :result, :partial => “the_answer”

You can replace the contents (innerHTML) of an element with the replace_html method. The only difference is that since it’s clear where should the new content go, there is no need for a position parameter – so replace_html takes only two arguments,
the DOM id of the element you wish to modify and a string or a hash of options to be passed to ActionView::Base#render.

Delay

You can delay the execution of a block of code with delay:

page.delay(10) { page.alert(‘Hey! Just waited 10 seconds’) }

delay takes one parameter (time to wait in seconds) and a block which will be executed after the specified time has passed – whatever else follows a page.delay line is executed immediately, the delay affects only the code in the block.

Reloading and Redirecting

You can reload the page with the reload method:

page.reload

When using AJAX, you can’t rely on the standard redirect_to controller method – you have to use the +page+’s instance method, also called redirect_to:

page.redirect_to some_url

Generating Arbitrary JavaScript

Sometimes even the full power of RJS is not enough to accomplish everything, but you still don’t want to drop to pure JavaScript. A nice golden mean is offered by the combination of <<, assign and call methods:

page << “alert(‘1+1 equals 3’)”

So << is used to execute an arbitrary JavaScript statement, passed as string to the method. The above code is equivalent to:

page.assign :result, 3 page.call :alert, ’1+1 equals ’ + result

assign simply assigns a value to a variable. call is similar to << with a slightly different syntax: the first parameter is the name of the function to call, followed by the list of parameters passed to the function.

Class Proxies
Element Proxies
Collection Proxies
RJS Helpers

I Want my Yellow Thingy: Quick overview of Script.aculo.us

Introduction

Visual Effects

Drag and Drop

Testing JavaScript

JavaScript testing reminds me the definition of the world ‘classic’ by Mark Twain: “A classic is something that everybody wants to have read and nobody wants to read.” It’s similar with JavaScript testing: everyone would like to have it, yet it’s not done by too much developers as it is tedious, complicated, there is a proliferation of tools and no consensus/accepted best practices, but we will nevertheless take a stab at it:

  • (Fire)Watir
  • Selenium
  • Celerity/Culerity
  • Cucumber+Webrat
  • Mention stuff like screw.unit/jsSpec

Note to self: check out the RailsConf JS testing video

Clone this wiki locally