-
Notifications
You must be signed in to change notification settings - Fork 3
Ajax on rails.textile
이 안내서는 레일스에 내장되어 있는 Ajax/JavsScript 기능성을 다루고 있어서 다양하고 동적인 AJAX 어플리케이션을 쉽게 만들수 있게 해 줄 것입니다. 여기서 다룰 주제는 다음과 같습니다.
- AJAX와 관련된 기술들에 대한 간략한 소개
- 자바스크립트를 레일스방식으로 다루기: Rails helpers, RJS, Prototype and script.aculo.us
- 자바스크립트 기능 테스트하기
‘show me the code’형의 사람이라면 이 부분을 건너띠고 RJS 부분으로 이동하고 싶어 할 수 있습니다. 그러나, 이 부분을 읽어 볼 것을 정말로 권하는데, 레일스에서 Ajax를 잘 이해하려면 여기서 언급되는 DOM의 기본개념, http 요청하는 방법, 그리고 기타 다른 토픽들이 필요할 것이기 때문입니다.
(= A synchronous Ja vaScript + X ML)
Ajax의 기본 용어설명, 웹어플리케이션을 만드는 새로운 방식
DOM 의 기본개념, DOM 이 만들어진 방법, DOM 의 속성, 특징, DOM 이 AJAX 의 핵심인 이유
통상적인 요청과 AJAX요청의 차이점, 레일스에서 AJAX 를 이해하는데 있어서의 이러한 차이점의 중요성(다음 섹션에 언급되는 *_remote 헬퍼메소드와 연관됨)
레일스의 기본 자바스크립트 프레임워크는 Prototype 입니다. Prototype 은 일반적으로 사용할 수 있는 자바스크립트 프레임워크로서 DOM 객체 조작과 AJAX 를 제공하며, 그외에도 유틸리티 함수로부터 객체지향적 구조에 이르는 자바스크립트 기능을 제공해 줌으로써 동적인 웹어프리케이션을 쉽게 개발할 수 있도록 하기 위해 만들어 졌습니다. Prototype 은 특정언어를 위해서 만들어진 것이 아니기 때문에, 이와 관련된 다수의 헬퍼메소드를 레일스가 제공해서 Prototype이 레일스 뷰 파일들과 매끄럽게 통합될 수 있도록 해줍니다.
이러한 헬퍼메소드에 접근하기 위해서 해야 할 것은 대개는 application.html.erb와 같은 웹어플리케이션의 최상위 레이아웃 파일에 Prototype 프레임워크를 아래와 같이 포함시키는 것입니다.
javascript_include_tag ‘prototype’
이제 레일스 어플리케이션에 AJAX에 대한 사랑을 더해 줄 준비가 된 것입니다.
아마도 가장 많이 사용하는 헬퍼메소드인 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’와 ‘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 If you want to fire a synchronous request for some obscure reason (blocking the browser while the request is processed and doesn’t return a status code), you can use the :type option with the value of :synchronous.
- If you specify the href parameter, the AJAX link will degrade gracefully, i.e. the link will point to the URL even if JavaScript is disabled in the client browser
- link_to_remote gains it’s AJAX behavior by specifying the remote call in the onclick handler of the link. If you supply html_options[:onclick] you override the default behavior, so use this with care!
We are finished with link_to_remote. I know this is quite a lot to digest for one helper function, but remember, these options are common for all the rest of the Rails view helpers, so we will take a look at the differences / additional parameters in the next sections.
There are three different ways of adding AJAX forms to your view using Rails Prototype helpers. They are slightly different, but striving for the same goal: instead of submitting the form using the standard HTTP request/response cycle, it is submitted asynchronously, thus not reloading the page. These methods are the following:
- remote_form_for (and it’s alias form_remote_for) is tied to Rails most tightly of the three since it takes a resource, model or array of resources (in case of a nested resource) as a parameter.
- form_remote_tag AJAXifies the form by serializing and sending it’s data in the background
- submit_to_remote and button_to_remote is more rarely used than the previous two. Rather than creating an AJAX form, you add a button/input
Let’s see them in action one by one!
In the last section we sent some AJAX requests to the server, and inserted the HTML response into the page (with the :update option). However, sometimes a more complicated interaction with the page is needed, which you can either achieve with JavaScript… or with RJS! You are sending JavaScript instructions to the server in both cases, but while in the former case you have to write vanilla JavaScript, in the second you can code Rails, and sit back while Rails generates the JavaScript for you – so basically RJS is a Ruby DSL to write JavaScript in your Rails code.
First we’ll check out how to send JavaScript to the server manually. You are practically never going to need this, but it’s interesting to understand what’s going on under the hood.
def javascript_test
render :text => “alert(‘Hello, world!’)”,
:content_type => “text/javascript”
end
(Note: if you want to test the above method, create a link_to_remote with a single parameter – :url, pointing to the javascript_test action)
What happens here is that by specifying the Content-Type header variable, we instruct the browser to evaluate the text we are sending over (rather than displaying it as plain text, which is the default behavior).
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”.
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”
In this section we’ll go through the methods RJS offers.
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.
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.
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.
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
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 ’ + resultassign 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.
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