2007년 2월 19일 월요일

ASP.NET 가이드 1. 자바 스크립트 사용하기

ASP.NET 가이드 1. 자바 스크립트 사용하기
저자
: 한동훈
0. 소개
웹 프로그래밍을 하는 경우 자바 스크립트를 사용하는 경우가 종종 발생한다. 자바 스크립트를 사용하는 데 있어 문제는 같은 문제에 대해 늘 인터넷을 찾아보고, 스크립트를 작성하는 일을 반복하는 데 있다. 때문에, 웹 개발자들은 자주 이용하는 자바 스크립트 사이트나 관련된 책을 책상에 항상 구비해 두고 있을 것이다.
ASP나 PHP와 같은 언어와 달리 ASP.NET은 객체지향 언어이고, 다양한 컨트롤들을 사용하고 있기 때문에 자바 스크립트 사용법에 차이점이 있다.
여기서는 앞으로 여러회에 걸쳐서 기본적인 자바 스크립트들을 ASP.NET에서 사용하는 방법들을 살펴볼 것이다. 그 이후에는 데이터그리드에서 다중 체크박스 처리와 같은 보다 심도 있는 내용을 다루도록 할 것이다.
1. 꼭 알아야 할 자바 스크립트
자바 스크립트를 잘 모르거나 기억이 흐릿한 분들을 위해 이 글을 이해하는데 꼭 필요한 기본적인 자바 스크립트를 설명할 것이다.(아주 기초적인 것만 다룬다)

위와 같은 HTML이 있다고 하자. 자바 스크립트는 script 태그 안에 위치한다.
자바 스크립트의 변수 선언은 var로 할 수 있으며, str과 str2의 선언에서 볼 수 있는 것처럼 문자열에는 " 또는 '을 모두 사용할 수 있다. HTML에서 속성이름="값"과 같은 형식으로 사용하기 때문에 자바 스크립트에서 문자열을 사용할 때는 '을 선호한다.
FORM 태그에 있는 요소들을 접근할 때는 document.forms[0]과 같은 형식을 사용한다. 하나의 페이지에 FORM 태그가 둘 이상 있는 경우에는 document.forms[0], document.forms[1]과 같은 형식으로 사용할 수 있다.
<form id=Forms1> 태그와 같이 ID가 지정된 경우에는 document.forms.Forms1, document.forms["Forms1"], document.forms.namedItem( "Forms1" )과 같은 형식으로 사용할 수 있다.
document.forms[0].txtName.value는 첫번째 폼에 있는 txtName 요소의 값을 가져오거나 설정하는 데 사용한다.
ASP.NET의 <ASP:TextBox runat=server id=txtID>는 브라우저에 <input type=text id=txtID>로 표시된다. 따라서, 스크립트에서 ASPX 페이지에 있는 텍스트 박스의 값을 이용하기 위해서는 document.forms[0].txtID.value과 같은 형태로 사용한다.
focus() 함수는 해당 텍스트 박스에 커서의 포커스를 이동시킨다. 여러분이 검색 사이트를 방문했을 때 키워드만 입력해서 검색할 수 있는 것도 focus() 함수 때문이다.
스크립트 축약하기
매번 document.forms[0].컨트롤ID 형태로 사용하는 것은 번거롭기 때문에 다음과 같은 형태를 더 빈번하게 사용한다.
<script language=javascript>
var theForm = document.forms[0];
theForm.txtName.value = 'Hello';
theForm.txtName.focus();
</script>
이 코드는 이전의 코드와 완전히 동일한 역할을 한다.
컨트롤 알아내기
ASP.NET에서는 대부분 하나의 FORM 태그만 있다는 가정하에 작업을 한다. 즉, ASP/PHP에서 처럼 여러 개의 FORM 태그를 두고 작업하지 않고 하나의 FORM 태그안에서 작업을 하는 경우가 대부분이다. 그러나, ASP.NET에서도 경우에 따라 여러 개의 폼을 사용할 수 있다. document.forms[0]나 document.forms.폼이름 과 같은 형태로 사용하는 것은 당장 자바 스크립트를 작성할 수 있게 해주나 자바 스크립트를 라이브러리로 사용하는 것을 어렵게 만든다.
원하는 컨트롤에 포커스를 주는 함수를 만들고 싶다고 할 때 어떤 FORM 태그안에 있는 컨트롤에 포커스를 주어야 할지, 또는 데이터 그리드안에 만든 텍스트박스에 어떻게 포커스를 주어야 할지 결정하기 어렵다.
즉, 동적으로 생성되는 컨트롤을 자바 스크립트로 제어하려면 document.forms[0]나 document.forms.Forms1과 같이 폼 인덱스나 폼 이름을 이용하는 자바 스크립트를 작성해서는 안된다.
<script language=javascript>
var ctl = document.getElementById( 'txtName' );
if( ctl != null )
{

ctl.value = 'Hello, cruel world';

ctl.focus();
}
</script>
위 예제에서는 컨트롤을 알아내기 위해 document.getElementById() 함수를 사용한다. 이 함수는 현재 브라우저에 있는 문서에서 ID를 사용해서 해당 컨트롤에 대한 참조를 얻어온다. 이 함수를 사용하기 위해서는 HTML에서 ID 속성만 사용하면 된다. ASP.NET에서는 웹 컨트롤의 ID 속성을 HTML의 ID 속성으로 변환해준다. - 대소문자를 구분하므로 ID 이름의 대소문자도 반드시 일치해야 한다.
현재 브라우저가 갖고 있는 문서에 ID에 해당하는 컨트롤이 없으면 null을 반환하기 때문에 반드시 null을 확인한 이후에 원하는 작업을 수행해야 한다. 그렇지 않으면, 자바 스크립트 오류가 발생한다.
getElementById의 호환성 문제
document.getElementById() 함수는 웹 표준안을 제정하고 있는 W3C의 DOM에 의해 정의된 것이며 오늘날 FireFox, Opera, IE 6+ 같은 대부분의 브라우저에서 잘 동작한다. 그러나 오래된 운영체제를 사용하고 있는 경우 이 함수가 동작하지 않는다. 특히, IE 5.5 이전 버전은 DOM Level 1을 완전히 지원하지 않는다. 때문에, 자바 스크립트의 기본적인 getElementById() 함수 대신에 이를 대체할 getElement() 함수를 작성하고, 이를 앞으로 BasePage의 모든 자바 스크립트에서 사용할 것이다.
<script language=javascript>
function getElement( id ) {

if( document.all ) return document.all( id );

if( document.getElementById ) return document.getElementById( id );
}
</script>
getElement()를 사용하여 작성하는 방법은 하위 버전 브라우저에 대한 동작을 보장하는 것 뿐만 아니라 비호환 브라우저가 등장하는 경우에 해당 브라우저를 지원하기 위해 모든 스크립트를 수정하는 대신 getElement() 스크립트만 수정하면 될 것이다.
이번 글을 이해하기 위해 알아야하는 자바 스크립트의 내용은 이것이 전부다. 자바 스크립트의 나머지는 C/Java/C#의 문법과 대부분 비슷하다. 차이점은 브라우저와 상호 작용하기 위해 document와 같은 몇 가지 객체들을 제공하고 있다는 점 뿐이다.
BasePage 소개
일반적으로 자바 스크립트를 작성하거나 어떤 기능들을 작성했을 때 각각의 페이지에 이러한 내용들을 추가해 주어야하는 번거로움이 있다. filename.js와 같은 외부 자바스크립트 파일을 작성한 경우에도 매번 이 스크립트를 사용할 것이라고 추가해 주어야한다.
게다가, 이렇게 사용하는 자바 스크립트는 ASP.NET의 서버측 코드(C#, VB.NET 소스)에서 사용할 수 없다. 개발자가 직접 태그에 이러한 코드들을 추가하기 위해 Control.Attributes.Add() 함수를 사용해야하고, 이 함수를 사용하기 위해 자바 스크립트의 정확한 이름까지 알고 있어야 한다.
이러한 불편함을 해소하기 위해 BasePage 클래스를 작성한다.
보통의 ASP.NET 페이지는 그림1의 왼쪽과 같이 System.Web.UI.Page 클래스를 상속한다. 여기에 자바 스크립트 지원을 비롯해서 사용자 인증, 예외 처리등을 위한 확장을 용이하게 하기 위해 그림1의 오른쪽 그림과 같이 클래스를 확장하였다.

[그림 1] BasePage 상속구조
BasePage 기본구조
먼저 해야 할 일은 Visual Studio .NET을 실행하고 [새 프로젝트]에서 그림2와 같이 [클래스 라이브러리] 프로젝트를 작성한다.

[그림2] 클래스 라이브러리 프로젝트 생성
그림1과 같이 System.Web.UI.Page 클래스를 상속하여 기능을 확장할 것이기 때문에 System.Web.dll에 대한 참조를 추가해야 한다.

[그림3] 참조 추가하기

[그림4] System.Web.dll 참조 추가
이와 같은 설정을 마치고 Mona.Web.UI.BasePage 클래스의 기본 뻐대를 다음과 같이 작성한다.

[그림5] Mona.Web.UI.BasePage
네임스페이스는 Mona.Web.UI로 정의하고 BasePage 클래스는 Page 클래스를 상속하였다. OnInit은 Page 클래스에서 각 컨트롤의 이벤트를 초기화하는 함수이며 이를 재정의하였다.
함수를 재정의할 때는 필요한 처리를 하고, 나머지 처리에 대해서는 상속 클래스에서 처리할 수 있도록 base.OnInit()과 같이 상속 클래스의 메서드를 호출하는 것을 잊지 말아야 한다. 완전히 재정의하는 경우에는 관계없지만 여기서의 목적은 완전히 재정의하는 것이 아니라 그 기능을 확장하는데 있기 때문에 이와 같이 한다.
자바스크립트: getElement 추가하기
페이지가 로딩되고 브라우저에 전달되기 전에 이벤트가 발생하는 PreRender 이벤트가 자바 스크립트를 추가하기 적합하다.
따라서, 다음과 같이 PreRender 이벤트를 정의한다.

[그림6] PreRender 이벤트 정의
앞으로 추가하게 될 다양한 스크립트 등록을 한 곳에 모아두기 위해 RegisterClientScript() 함수를 작성하였고, BasePage_PreRender()에서 이를 호출하도록 하였다.
자바 스크립트를 추가하기 위해 AddGetElementScript()를 추가하였다. 하나의 자바 스크립트에 대해서는 하나의 함수로 독립시켰다. 필요에 따라 특정 스크립트의 추가와 제거를 손쉽게 하기 위해 이와 같이 스크립트를 나누었다.
앞에서 설명한 것처럼 getElement() 함수는 ASP.NET 페이지의 컨트롤과 상호 작용할 필요가 없기 때문에 단순히 스크립트를 등록하기만 한다. 다음은 AddGetElementScript()의 정의다.
AddGetElementScript() 함수

[그림7] AddGetElementScript() 함수 정의
자바 스크립트 함수 이름과 스크립트를 등록하기 위한 이름을 각각 GetElementFunctionName과 GetElementScriptName 문자열 상수로 만들었다. 다른 스크립트에서도 __getElement() 라는 스크립트를 직접 이용하는 대신 GetElementFunctionName이라는 문자열 상수를 참조하여 작성할 것이다.
자바 스크립트를 등록하기 전에 IsClientScriptBlockRegistered()를 사용하여 스크립트 등록 여부를 확인하고, 등록되어 있지 않은 경우에 RegisterClientScriptBlock()을 사용하여 스크립트를 등록한다. 이와 같은 방법은 Mona.Web.UI.BasePage를 상속하여 클래스를 확장하는 경우에 충돌없이 자바스크립트를 등록하고 사용할 수 있게 해준다.(예를 들어, 사용자 요청을 처리하는 동안 피드백을 보여주거나 에러 처리에 대한 정보를 처리하기 위해 BasePage 클래스를 상속하여 확장하는 경우가 발생할 수 있다)
다음은 원하는 컨트롤에 커서를 위치시키기 위한 자바 스크립트 등록과 SetFocus()를 만들어 볼 것이다.
AddSetFocusScript() 함수
이 자바 스크립트를 사용하는 목적은 원하는 텍스트 박스에 커서를 위치 시키기 위해 반복적으로 자바 스크립트를 작성하지 않기 위한 것이다. 또한, 사용자가 쿠키에 ID를 저장한 경우에 비밀번호를 바로 입력할 수 있게 비밀번호 입력상자에 커서를 위치시키고, 쿠키에 ID를 저장하지 않은 경우 ID 입력 상자에 커서를 위치시키는 것과 같은 동적 스크립팅 처리를 하기 위한 것이다.

[그림8] RegisterClientScript()에 AddSetFocusScript() 추가
RegisterClientScript() 함수에 AddSetFocusScript()를 추가한다.

AddSetFocusScript() 함수 역시 SetFocusScriptName과 SetFocusFunctionName 문자열 상수를 이용해서 스크립트 이름과 함수 이름을 정의한다. 빨간 테두리로 되어 있는 부분이 __setFocus() 함수를 정의하는 부분이다. 노란색 테두리는 사용자가 원하는 컨트롤에 커서를 이동시킨다. 웹페이지에서는 __setFocus( 'txtName' ); 과 같이 된다.
아직, focusedControl 변수를 선언하지 않았다는 것을 알 수 있다. 실제로, 코드에서 사용할 SetFocus() 함수는 이 컨트롤의 이름을 전달받아 내부변수 focusedControl에 설정하는 역할만 한다.
SetFocus() 함수

먼저 focusedControl 변수를 선언하고 생성자에서 내부 변수의 값을 초기화한다. 초기화하지 않으면 NullReferenceException이 발생하므로 주의하기 바란다.
RegisterStartupScript()와 RegisterClientScriptBlock()의 차이
AddGetElementScript()와 AddSetFocusScript()에서 자바 스크립트를 등록하기 위해 서로 다른 함수를 사용했다.
얼핏 생각하기에 두 함수간에 차이점은 없다.(정말?)
RegisterClientScriptBlock() 함수는 시작 FORM 태그(<FORM>) 다음에 스크립트가 생성되지만, RegisterStartupScript() 함수는 닫는 FORM 태그(</FORM>) 바로 앞에 스크립트가 생성된다.
이 두 스크립트의 차이점을 이해하기 위해 자바 스크립트를 설명하며 살펴본 예제를 다시 살펴보자.

여기서는 자바 스크립트에서 이미 있는 txtName 요소를 사용하기 때문에 문제가 발생하지 않는다. 그러나 스크립트와 태그의 순서를 다음과 같이 변경하면 txtName 요소가 선언되기 전에 자바 스크립트가 위치하기 때문에 오류가 발생한다.

마찬가지로 BasePage에서 SetFocus()와 같이 폼 요소들을 참조하는 스크립트를 작성하는 경우에 해당 요소들이 모두 화면에 출력된 다음에 자바 스크립트가 작성되어야 참조 오류가 발생하지 않는다.
앞으로 소개하게 될 자바 스크립트는 모두 폼 요소들을 참조하기 때문에 RegisterStartupScript()를 사용하여 스크립트를 등록한다.
BasePage 테스트: TestBasePage
새로운 ASP.NET 웹 프로젝트를 BasePage 프로젝트에 추가한다.
참조에는 BasePage 프로젝트 참조를 다음과 같이 추가한다.

참조를 추가했으면 TestBasePage.aspx 페이지를 만들고 다음과 같이 폼을 작성한다.

TestBasePage.aspx.cs 소스

소스 코드에서 직접 작성해야 하는 부분은 빨강 테두리로 표시했으며, Mona.Web.UI.BasePage를 상속하고 있다.
이와 같이 작성하고 테스트 해보면 페이지를 처음 로딩할 때는 첫번째 텍스트 박스에 포커스가 위치하고 버튼을 클릭하면 두번째 텍스트 박스에 포커스가 위치하는 것을 알 수 있다.
마찬가지로, 생성된 HTML에서 GetElement와 SetFocus 자바 스크립트의 생성위치를 비교해보기 바란다.
BasePage 버전 관리
System.Web.UI.Page를 BasePage로 대체하기로 결정했다면 여러 프로젝트에서 같은 DLL을 참조하여 사용하게 될 것이다. BasePage의 개선이 자주 발생하지 않는다해도 매번 컴파일시 변경되는 버전은 참조 오류를 일으킨다. 따라서 BasePage 프로젝트의 AssemblyInfo.cs 에서 AssemblyVersion 특성을 다음과 같이 변경하여, 고정된 버전번호를 부여하도록 한다.
[assembly: AssemblyVersion("1.0.0.0")]
SetFocus() 함수와 ASP.NET 2.0
반가운 소식은 앞서 구현한 SetFocus() 함수의 역할을 대신할 함수가 ASP.NET 2.0에 포함되었다는 것이다. 현재 .NET Framework 2.0 베타에서도 이 함수를 사용할 수 있다. System.Web.UI.Page.SetFocus( string controlName )이 바로 그 함수다. ASP.NET 2.0이 도입될 경우에도 BasePage의 SetFocus() 함수를 그대로 사용할 수 있으며, 원한다면 AddSetFocusScript() 함수를 제거하는 것으로 ASP.NET 2.0에 대비할 수 있을 것이다.
다음시간에는 자동탭 넘김, 숫자 입력 텍스트 박스, 문자 입력 텍스트 박스 등에 대해서 살펴볼 것이다.
참고자료
BasePage 소스코드 다운로드

Build Your ASP.NET Pages on a Richer Bedrock

JavaScript: The Definitive Guide, 4th Edition, Ch. 17
JavaScript & DHTML Cookbook, Ch. 8

출처 : Tong - 夢魂™님의 # 기술 자료통