 |  |
|
난이도 : 중급 Michael Galpin, 개발자, eBay
옮긴이: 장동수 dwkorea@kr.ibm.com
2008 년 10 월 21 일 지금 바로 이름이 떠오르는 자바(Java™) 웹 개발 프레임워크, 라이브러리, 툴킷만 해도 엄청나게 많습니다. 뭐가 뭔지, 어떤 것이 문제 해결에 도움이 될지 알아내는 것만 해도 버거운 일이죠. 그러나 Ajax 개발을 하고 있다면 반드시 알아두어야 할 라이브러리가 바로 DWR(Direct Web Remoting)입니다. 이 라이브러리는 자바 언어와 자바의 웹 기술을 사용하여 Ajax 개발을 더 쉽게 해줍니다. 이 라이브러리는 Ajax와 자바 웹 애플리케이션을 긴밀하게 통합하는 방법에 있어 기준을 제시합니다. DWR은 Ajax 기술들의 광범위한 연합인 Dojo 재단에 합류했습니다. 이 기사에서는 DWR을 사용하면 Ajax가 얼마나 간단해지는지 알아보겠습니다.
이 글은 Ajax 애플리케이션 제작에 사용할 수 있는 인기있는 자바스크립트 라이브러리들에 대해 알아보는 연재의 세 번째이자 마지막 기사다. Part 1에서는 Prototype 라이브러리를 사용하여 노래를 관리하는 웹 애플리케이션을 만들어 보았다. Part 2에서는 script.aculo.us를 사용하여 사진을 관리하는 웹 애플리케이션을 만들어 보았다. 이번에는 DWR을 사용하면 Ajax가 얼마나 간단해지는지 알아보자.
이 기사에서는 DWR 2.0을 사용한다. 예제 코드는 제네릭스(generics)와 어노테이션(annotation)을 사용하므로 자바 5 이상이 필요하다. 예제에서는 MySQL 5.12와 톰캣(Tomcat) 6.0.14를 사용하고 있지만, 쉽게 교체할 수 있을 것이다. 데이터베이스 접근을 위해 JPA(Java Persistence API)를 사용하며, JPA 구현체는 OpenJPA 1.0을 사용한다. 하이버네이트(Hibernate)나 Kodo 등의 다른 JPA 구현체로 바꿔도 된다. 이 기사에서는 Ajax 디버깅을 위한 멋진 도구인 파이어버그(Firebug: 파이어폭스용 플러그인)를 사용한다. 이 도구들에 대한 링크는 참고자료에 나와 있다.
DWR 소개
Ajax 애플리케이션이 처음 등장했을 때는 마법처럼 보였지만, 다행히도 개발하는 과정은 직관적이다. 모든 Ajax 상호작용은 서버 측 엔드포인트(endpoint: 웹 서비스에서 빌려온 용어)와 그 엔드포인트를 호출하는 클라이언트 측 코드가 필요하다. 또한 클라이언트와 서버가 주고받는 데이터를 직렬화(serialize)하는 코드도 필요하다. 서버 측 엔드포인트는 일반적인 서비스(RESTful이든 아니든)면 되지만, 클라이언트 측에는 종종 요구에 부합하는 좀 더 구체적인 코드가 필요하다. 강한 결합(tight coupling)을 피해야 할 때도 있지만, 필요할 때도 있다. 후자라면 DWR이 준비된 해결책이다. DWR은 선언적인 방법으로(declaratively) 서버 측 코드를 Ajax 엔드포인트로 만들 수 있고, 모든 부가적인 설비도 자동으로 만들어 준다. 지금부터 구체적인 예제를 통해 DWR이 어떻게 동작하는지 알아보자.
예제 애플리케이션: Ajax 쪽지 게시판
여기서 만들어 볼 예제는 간단한 쪽지 게시판이다. DWR을 통한 Ajax에 집중하기 위해, 데이터 모델을 극도로 단순하게 했다. DWR을 사용하여 애플리케이션에 Ajax를 도입하는 방법을 알아보기 위해 먼저 예제의 백엔드부터 살펴보자.
백엔드 설정하기
쪽지 게시판을 위한 데이터베이스 테이블부터 만들어보자. 이 테이블을 생성하는 SQL 스크립트가 Listing 1이다.
Listing 1. Messages 테이블 SQL 스크립트
CREATE TABLE 'messages' (
'id' int(11) NOT NULL auto_increment,
'title' varchar(40) NOT NULL,
'body' text,
'created_at' timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
'author' varchar(40) NOT NULL default 'anonymous',
PRIMARY KEY ('id')
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
여기서 알아야 할 것은 쪽지 게시판의 쪽지를 위해 제목(title), 내용(body), 글쓴이(author)가 필요하다는 것뿐이다. SQL 스크립트가 나머지를 처리한다. 이 스크립트는 MySQL 용이지만, 다른 RDBMS에도 쉽게 적용할 수 있다. 데이터베이스 접근을 위해 JPA를 사용한다. 테이블에 대응하는 자바 클래스가 Listing 2다.
Listing 2. Message 자바 클래스
package org.developerworks.msgb;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Column;
import org.directwebremoting.annotations.DataTransferObject;
@DataTransferObject
@Entity
@Table(schema="msgb", name = "messages")
public class Message {
@Id
@GeneratedValue(strategy=IDENTITY)
private int id;
@Column(name="title")
private String title;
@Column(name="author")
private String author;
@Column(name="body")
private String body;
@Column(name="created_at")
private Date createdAt;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
|
대부분 뻔한 코드(boilerplate code)다. getter와 setter를 가진 몇 개의 필드뿐이다. 어노테이션은 대부분 자바 필드와 데이터베이스 컬럼을 매핑하기 위한 JPA 표준 어노테이션이다. 알아두어야 할 특이한 어노테이션이 하나 있는데, 바로 @DataTransferObject 어노테이션이다. 이 DWR 어노테이션은 이 클래스가 Ajax 응답의 일부로 자동으로 직렬화(marshaling)될 수 있음을 DWR에 알려준다. DWR은 자바 타입을 직렬화하기 위한 여러 가지 변환기(converter)를 포함하고 있다. 어노테이션을 사용하여 특정 필드가 직렬화에 포함되는지도 지정할 수 있다. 데이터베이스 접근에 대한 작업이 끝났으면, 서비스를 만들어 보자.
원격 서비스 만들기
예제는 단순한 두 가지 기능밖에 없다. 쪽지 게시판에 올라온 모든 쪽지 목록을 보여주고, 사용자가 새로운 쪽지를 올릴 수 있다. 이 점을 염두에 두고, 예제의 서비스 코드인 Listing 3을 살펴 보자.
Listing 3. MessageService 클래스
package org.developerworks.msgb;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;
@RemoteProxy
public class MessageService {
private EntityManagerFactory factory;
private EntityManager em;
public MessageService(){
factory = Persistence.createEntityManagerFactory("msgb");
em = factory.createEntityManager();
}
@RemoteMethod
@SuppressWarnings("unchecked") // thanks type erasure
public List<Message> getMessages(){
List<Message> messages = em.createQuery("select m from
Message m").getResultList();
return messages;
}
@RemoteMethod
public void saveMessage(Message msg){
em.getTransaction().begin();
em.persist(msg);
em.getTransaction().commit();
}
}
|
이 코드는 쪽지를 조회하고 생성하는 표준 JPA 코드다. 앞의 코드와 마찬가지로 주의깊게 볼 부분은 어노테이션이다. 클래스에 RemoteProxy 어노테이션이 붙어 있다. 이 어노테이션은 원격 클라이언트가 이 클래스의 메서드를 호출할 수 있음을 DWR에 알려준다. 물론, 그 원격 호출은 DWR을 통한 Ajax 호출이다. 각 메서드는 RemoteMethod 어노테이션을 사용하여 명시적으로 공개된다. 공개하고 싶지 않은 메서드가 있다면 어노테이션을 빼기만 하면 된다. 지금까지 작성한 코드는 일반적인 백엔드 코드와 추가적인 몇 가지 어노테이션뿐이다. DWR을 사용하기 위해 해야 할 작업이 하나 더 있다.
DWR 서블릿
DWR은 Ajax 요청을 받고 응답하기 위해 자바 서블릿을 사용한다. 이 서블릿은 DWR 라이브러리에 포함되어 있으므로 웹 애플리케이션의 web.xml 파일만 바꿔주면 된다. 예제에서 사용하는 web.xml이 Listing 4다.
Listing 4. 쪽지 게시판을 위한 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="msgb" version="2.5">
<display-name>MessageBoard</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
<display-name>DWR Servlet</display-name>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>
org.directwebremoting.servlet.DwrServlet
</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>classes</param-name>
<param-value>org.developerworks.msgb.MessageService,
org.developerworks.msgb.Message</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
</web-app>
|
DWR 서블릿이 /dwr/로 시작하는 모든 요청을 처리하도록 설정했다. 주목해 볼 만한 것은 init-params들이다. classes 매개변수에는 DWR 어노테이션을 가진 모든 클래스를 쉼표로 구분하여 나열하면 된다. 어노테이션을 좋아하지 않는다면 XML 파일을 사용할 수도 있다. 이것으로 백엔드 작업은 모두 끝났다. 이제부터 프론트엔드를 만들어 보자.
프론트엔드 만들기
DWR을 애플리케이션의 백엔드에 적용하는 것은 무척 쉽다. 몇 가지 선언과 DWR 서블릿을 설정하는 것이 전부다. 그렇다면 프론트엔드는 어떨까? 자바스크립트 툴킷은 대부분 개발자들이 사용할 라이브러리를 포함하고 있다. 개발자는 해당 라이브러리를 웹 페이지에 포함시키고, 문서를 보면서 사용법을 익힌다. DWR은 다르다.
DWR은 자바스크립트 코드를 동적으로 생성한다. DWR을 사용하면 그 자바스크립트를 사용하는 방법도 쉽게 알 수 있다. Listing 4의 web.xml 파일을 주의깊게 살펴보자. debug라는 init-param이 보이는가? 이 매개변수를 사용하면 DWR이 생성한 동적인 자바스크립트를 자체 검사할(introspect) 수 있다. 웹 애플리케이션을 배포하고, http://<root>/<web_app_name>/dwr을 방문하면 그림 1과 같은 페이지를 볼 수 있다.
그림 1. DWR 디버그 화면
이 페이지는 @RemoteProxy 어노테이션이 붙은 모든 자바 클래스의 목록을 표시한다. 클래스를 클릭하면 그림 2와 같은 더 자세한 정보를 볼 수 있다.
그림 2. MessageService 자바스크립트 정보
이 페이지는 동적인 자바스크립트를 참조하는 방법을 보여준다. 반드시 포함시켜야 하는 핵심 라이브러리(engine.js)와 애플리케이션에 따라 달라지는 동적인 라이브러리(MessageService.js)가 있다. 여러 가지 도우미 함수를 제공하는 선택적인 유틸리티 라이브러리도 있다.
이제 DWR 자바스크립트를 참조하는 방법을 알았으니 사용하는 법을 알아보자. MessageBoard 페이지를 사용하여 시험해 볼 수 있다(그림 3).
그림 3. DWR Ajax 테스팅
이 화면은 getMessages()를 호출하는 Ajax 요청의 결과를 보여준다. 데이터는 자바스크립트 객체(JSON)의 배열로 표시된다. API의 반환 값을 알았으니 호출하는 방법도 알아보자. Listing 5는 페이지 소스다.
Listing 5. MessageBoard 테스트 페이지 소스
<li>
getMessages( );
<input class='ibutton' type='button' onclick='MessageService.getMessages(reply0);'
value='Execute' title='Calls MessageService.getMessages().
View source for details.'/>
<script type='text/javascript'>
var reply0 = function(data)
{
if (data != null && typeof data == 'object')
alert(dwr.util.toDescriptiveString(data, 2));
else dwr.util.setValue('d0', dwr.util.toDescriptiveString(data, 1));
}
</script>
<span id='d0' class='reply'></span>
</li>
<li>
saveMessage( <input class='itext' type='text' size='10' value=''
id='p10' title='Will be converted to: org.developerworks.msgb.Message'/> );
<input class='ibutton' type='button' onclick='MessageService.saveMessage
(objectEval($("p10").value), reply1);' value='Execute' title='Calls
MessageService.saveMessage(). View source for details.'/>
<script type='text/javascript'>
var reply1 = function(data)
{
if (data != null && typeof data == 'object') alert(dwr.util.
toDescriptiveString(data, 2));
else dwr.util.setValue('d1', dwr.util.toDescriptiveString(data, 1));
}
</script>
<span id='d1' class='reply'></span>
</li>
|
예제에서는 getMessages를 호출하기 위해 MessageService.getMessages(callbackFunction)을 사용했다. callbackFunction에 전달된 값은 앞에서 메서드를 시험하면서 살펴 보았다. 이제 웹 페이지를 만들 수 있는 준비가 끝났다. Listing 6을 살펴 보자.
Listing 6. MessageBoard 웹 페이지
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>The developerWorks Message Board</title>
<script type="text/javascript" src="/MessageBoard/dwr/interface/
MessageService.js"></script>
<script type="text/javascript" src="/MessageBoard/dwr/
engine.js"></script>
<script type="text/javascript" src="/MessageBoard/dwr/
util.js"></script>
<script type="text/javascript">
var columns = ["title", "author", "body", "createdAt"];
function loadData(){
MessageService.getMessages(addRows);
}
// messages should be an array of Message hashes
function addRows(messages){
var cellfuncs = [];
for each (var prop in columns){
cellfuncs.push(createFunction(prop));
}
dwr.util.addRows("messageTableBody", messages, cellfuncs);
}
function createFunction(prop){
return function(msg) { return msg[prop]; };
}
function sendMsg(){
var msg = {};
msg.title = dwr.util.getValue("title");
msg.author = dwr.util.getValue("author");
msg.body = dwr.util.getValue("body");
MessageService.saveMessage(msg);
msg.createdAt = Date();
var messages = [msg];
addRows(messages);
clearForm();
}
function clearForm(){
dwr.util.setValue("title","");
dwr.util.setValue("author","");
dwr.util.setValue("body","");
}
</script>
<style type="text/css">
label {
float: left;
text-align: right;
margin-right: 15px;
width: 100px;
}
#messageTableHead {
font-weight: 900;
color:navy;
}
body {
color:MidnightBlue;
background-color:PaleTurquoise;
margin:20px;
padding:0px;
font:11px verdana, arial, helvetica, sans-serif;
}
.pageTitle {
margin:0px 0px 15px 0px;
padding:0px;
font-size:28px;
font-weight:900;
color:#aaa;
}
#formDiv{
padding-top:12px;
color:Indigo;
}
</style>
</head>
<body onLoad="loadData()">
<div class="pageTitle">The developerWorks Message Board</div>
<div class="pageContent">
<table id="messageTable" border="1" cellpadding="4">
<thead id="messageTableHead">
<tr>
<td>Title</td>
<td>Author</td>
<td>Message</td>
<td>Posted</td>
</tr>
</thead>
<tbody id="messageTableBody">
</tbody>
</table>
</div>
<div id="formDiv">
<form id="messageForm">
<label>Message Title:</label><input type="text"
name="title" id="title"/><br/>
<label>Your Name:</label><input type="text"
name="author" id="author"/><br/>
<textarea name="body" cols="80" rows="15"
id="body"></textarea><br/>
<input type="button" id="msgBtn" value="Add Message"
onClick="sendMsg()"/>
</form>
</div>
</body>
</html>
|
코드를 분석해 보자. 먼저 이 페이지는 JSP나 JSF 페이지가 아니라 정적인 HTML과 자바스크립트, CSS로 이루어진 단순한 HTML 파일이다. 페이지를 불러올 때 loadData() 함수를 호출한다. 이 함수는 앞에서 배웠던 MessageService.getMessages()를 호출한다. 그러면 DWR이 알아서 Ajax 요청을 보내고 그 응답을 addRows 콜백 함수에 전달한다.
addRows() 함수는 서버로부터 데이터를 받아서, DWR이 제공하는 dwr.util.addRows 함수를 호출한다. 이 함수는 지정한 ID를 가진 HTML 테이블(table), 테이블 헤더(header), 테이블 푸터(footer), 테이블 본체(body)에 행을 추가한다. 데이터를 추출해 테이블의 셀로 변환하기 위해 함수의 배열(코드에서는 cellfuncs)을 사용하고 있다. 결과적으로 테이블의 (i,j) 번째 셀의 데이터는 cellfuncs[j](messages[i])가 될 것이다. 이것은 데이터를 테이블로 매핑하는 매우 간결한 방법이다. 페이지를 브라우져로 열어보면 그림 4와 같은 화면을 볼 수 있다.
그림 4. MessageBoard 페이지
이 페이지는 DWR을 통한 Ajax를 사용하여 데이터를 비동기로 가져온다. 쪽지 목록은 표시했지만, 새로운 쪽지는 어떻게 올려야 할까? 페이지의 아래쪽에 있는 폼을 통해 그 방법을 알 수 있다. Add Message를 클릭하면 DWR의 유틸리티 함수인 dwr.util.getValue를 호출하여 폼에서 데이터를 가져온다. 이 함수는 div, 텍스트 입력, 체크박스, 선택 목록 등 어떤 HTML 태그에도 사용할 수 있다. 폼에서 가져온 데이터를 자바스크립트 객체에 넣은 다음, MessageService.saveMessage()를 호출한다. 이 경우에는 응답이 없기 때문에(void) 핸들러를 지정하지 않았다. 그 대신, 앞에서 테이블에 새로운 행을 추가하기 위해 사용했던 addRows 함수를 재사용했다. 이렇게 하면 UI의 반응이 빨라진다.
폼에 값을 채우고 Add Message를 눌러서 시험해 볼 수 있다. 파이어버그 같은 도구를 사용하여 HTTP 통신 내용을 살펴보면 도움이 된다. Listing 7은 saveMessage를 호출할 때 서버로 보내진 데이터다.
Listing 7. saveMessage 호출
callCount=1
page=/MessageBoard/
httpSessionId=
scriptSessionId=B88B0681A9BB674C14786B7DCA3EA6E3153
c0-scriptName=MessageService
c0-methodName=saveMessage
c0-id=0
c0-e1=string:The%20One%20I%20Love
c0-e2=string:Michael
c0-e3=string:This%20goes%20out%20to%20the%20one%20I%20love.
%20This%20one%20goes%20out%20to%20the%20one
%20I%20left%20behind.
c0-param0=Object_Object:{title:reference:c0-e1,
author:reference:c0-e2, body:reference:c0-e3}
batchId=1
|
Listing 7은 DWR이 주고 받는 데이터 형식을 보여준다. DWR 같은 프레임워크의 관점에서는 별로 중요하지도 않고, 신경 쓸 필요도 없지만, 어떻게 동작하는지 알아보는 일도 흥미롭다.
요약
Ajax 개발의 다양한 부분을 더 쉽게 만들어주는 다양한 Ajax 툴킷이 있다. DWR은 자바 웹 애플리케이션을 위한 일석이조 툴킷이다. DWR을 사용하면 서버 측과 클라이언트 측 둘 다 쉽게 만들 수 있다. 서버 측에서는 몇 개의 자바 어노테이션만 붙이면 어떤 서비스라도 Ajax 서비스로 만들 수 있다. 클라이언트 측에서는 서버에 있는 서비스와 똑같은 API를 자동으로 만들어주므로, 콜백 함수만 추가하면 된다. 이보다 더 간단할 수 없다.
다운로드 하십시오 | 설명 | 이름 | 크기 | 다운로드 방식 |
|---|
| Part 3 예제 코드 | wa-aj-ajaxpro3.zip | 1087KB | HTTP |
|---|
참고자료 교육
- "DWR을 사용하여 Ajax 기반 파일 업로드 포틀릿 개발하기"(한국 developerWorks, 2007년 10월)에서 포틀릿 프레임워크에서 DWR을 사용하는 방법을 알아보자.
- "자바 개발자를 위한 Ajax: Jetty와 DWR을 사용하여 확장 가능한 Comet 애플리케이션 개발하기"(한국 developerWorks, 2007년 9월)에서 DWR이 Reverse Ajax(일명 Comet)를 어떻게 구현하는지에 대해 알아보자.
- "Kick-start your Java apps"(developerWorks, 2007년 12월)에서 DWR을 사용하여 기존 웹 응용 프로그램을 현대적으로 만드는 방법을 알아보자.
- "Design enterprise applications with the EJB 3.0 Java Persistence API"(developerWorks, 2006년 3월)에서 JPA를 배워보자.
- script.aculo.us는 루비 온 레일즈의 Ajax 지원의 핵심 요소다. "Crossing borders: Ajax on Rails"(developerWorks, 2006년 12월)에서 더 자세히 알아보자.
- script.aculo.us는 다른 많은 자바스크립트 라이브러리들과 함께 사용할 수 있다. "자바 개발자를 위한 Ajax: 구글 웹 툴킷(Google Web Toolkit) 연구"(한국 developerWorks, 2006년 8월)에서 script.aculo.us의 시각 효과들을 구글 웹 툴킷과 함께 사용하는 방법을 배워보자.
- "Ajax와 XML: 다섯 개의 추천할 만한 Ajax 위젯"(한국 developerWorks, 2007년 5월)에서 script.aculo.us의 제자리 편집 컨트롤에 대해 알아보자.
- "Ajax -- a guide for the perplexed"(developerWorks, 2007년 7월)에서 script.aculo.us를 포함한 인기있는 Ajax 라이브러리에 대해 알아보자.
- "Ajax와 XML: 다섯 개의 일반적인 Ajax 패턴"(한국 developerWorks, 2007년 4월)에서 이 글에서 언급한 몇 가지 Ajax 패턴에 대해 더 자세히 알아보자.
- 이 글에서 사용된 서버 스크립트는 REST 프로토콜을 따른다. "RESTful Web services and their Ajax-based clients"(developerWorks, 2007년 7월)에서 REST와 Ajax를 함께 사용하는 방법을 더 자세히 알아보자.
- Prototype은 루비 온 레일즈의 Ajax 지원의 핵심 요소다. "Crossing borders: Ajax on Rails"(developerWorks, 2006년 12월)에서 더 자세히 알아보자.
- "Ajax -- a guide for the perplexed"(developerWorks, 2007년 7월)에서 Prototype을 포함한 인기있는 Ajax 라이브러리에 대해 알아보자.
- "이클립스용 Ajax 툴킷 프레임워크"(2007년 6월): Dojo, Zimbra, Rico 등의 오픈 소스 Ajax 툴킷을 지원하기 위해 이클립스 WTP를 확장한다.
- Simon Willison가 쓴 "A (Re)-Introduction to JavaScript"를 읽어보자.
- IBM developerWorks의 Ajax 참고자료 센터
- W3Schools는 모든 핵심 Ajax 기술(자바스크립트, CSS, HTML, DOM, XML 등)에 대한 온라인 참조 정보를 제공한다.
- developerWorks 기술 행사와 웹 캐스트
- 포드캐스트
제품 및 기술 얻기
필자소개  | 
|  | Michael Galpin은 1998년부터 전문적으로 자바 소프트웨어를 개발하고 있으며 이베이에서 근무중이다. 캘리포니아 공대에서 수학을 전공하였다. |
기사에 대한 평가
 |
| 이 문서 북마킹 하기
|
|  |