제공 : 한빛 네트워크
저자 : Howard Feldman
역자 : 추홍엽
원문 : Writing Advanced JavaScript
팝업 메뉴
다음이자 이 글에서 마지막으로 볼 위젯은 이전 예제보다 훨씬 더 복잡한 팝업 메뉴이다. 먼저 위젯의 동작을 보자:
이곳을 클릭하면 메뉴가 뜬다: X (※ 아래 코드 참고)
For menu click here:
이 예제는 자바스크립트 함수와 몇몇 복잡한 이벤트 핸들링을 포함하기 때문에 더 복잡하다. 다음은 사용된 자바스크립트 코드와 HTML이다:
function getElementAbsPosX(el)
{
var dx = 0;
if (el.offsetParent) {
dx = el.offsetLeft + 8;
while (el = el.offsetParent) {
dx += el.offsetLeft;
}
}
return dx;
}
function getElementAbsPosY(el)
{
var dy = 0;
if (el.offsetParent) {
dy = el.offsetTop + el.offsetHeight / 2;
while (el = el.offsetParent) {
dy += el.offsetTop;
}
}
return dy;
}
function GetAbsWindowBottom()
{
// 팝업 윈도와 브라우저 윈도의 하단을 계산한다.
// 모든 브라우저에서 다를 절대좌표지만 아래의 것은 보통 정확해야 한다!
var abswindowbottom = 0;
if (typeof(window.innerHeight) == "number")
abswindowbottom = window.innerHeight;
else if (document.documentElement && document.documentElement.clientHeight)
abswindowbottom = document.documentElement.clientHeight;
else if (document.body && document.body.clientHeight)
abswindowbottom = document.body.clientHeight;
if (typeof(window.pageYOffset) == "number")
abswindowbottom = abswindowbottom + window.pageYOffset;
else if (document.body && document.body.scrollTop)
abswindowbottom = abswindowbottom + document.body.scrollTop;
else if (document.documentElement && document.documentElement.scrollTop)
abswindowbottom = abswindowbottom + document.documentElement.scrollTop;
return abswindowbottom;
}
function PopupMenu(name, vis)
{
var el = name + "menu";
var tag = name + "menuroot";
if (!document.getElementById(el)) // 메뉴 오브젝트를 발견하지 못할때
return;
if (vis == 0) { // 메뉴를 숨김
document.getElementById(el).style.visibility = "hidden";
return;
}
// 메뉴시작 위치를 얻어옴
var pos = document.getElementById(tag);
var dx = getElementAbsPosX(pos);
var dy = getElementAbsPosY(pos);
// 메뉴 하단과 윈도 하단을 비교
var abspopupbottom = dy + document.getElementById(el).clientHeight + 10;
var abswindowbottom = GetAbsWindowBottom();
// 메뉴가 윈도 하단 아래로 벗어나면 위로 올린다!
if (abspopupbottom > abswindowbottom)
dy = dy - (abspopupbottom - abswindowbottom);
// 마지막으로 메뉴 위치를 설정하고 보이게 한다.
document.getElementById(el).style.left = dx + "px";
document.getElementById(el).style.top = dy + "px";
if (vis > 0)
document.getElementById(el).style.visibility = "visible";
}
메뉴에 대한 실제 HTML은 이전 예제처럼 div 태그가 허용되는 아무곳에나 위치할 수 있다.
그리고 메뉴를 인스턴스화 한다.
먼저 메뉴를 살펴보자. div 태그 사이에 원하는 모든것을 놓을 수 있으며 div 태그 속성에서 모든 마법이 일어난다. 특히, 이전 예제에서처럼 절대 포지셔닝을 사용해야 다른 텍스트와 이미지가 나타남에도 불구하고 메뉴의 정확한 위치를 조정할 수 있음에 주목한다. 또한 메뉴가 visibility: hidden로 시작하는지 확인하고 나중에 쉽게 다룰수 있도록 ID는 testmenu로 부여하도록 한다.
div 태그에서도 처음 사용하는 setTimeout와 clearTimeout 함수를 마주치게 되는데 이것은 지정된 시간 후에 단 한번만 명령이 실행된 다는 것만 제외하면 이전 예제에서 봤던 . setInterval 함수와 유사하게 동작한다. 이 예제에서는 메뉴영역 밖으로 마우스가 나간 1,500ms 후에 메뉴가 닫히고, (메뉴가 열린 상태에서) 마우스가 메뉴영역 안으로 다시 들어오면 그 효과가 취소되게 하는데 사용되었다. 자바스크립트 파일에서 정의된 tout은 타이머 오브젝트에 대한 포인터를 유지하는 전역 변수이다. onmouseout 이벤트가 발생할 때 (div태그--메뉴--로 둘러싸인 영역 밖으로 마우스가 나가면) PopupMenu("test", 0)은 1,500ms 후에 호출되도록 설정된다. (인용부호 내에서 다시 인용부호를 쓰기때문에 이스케이프 시켜야 하는 것에 주의한다.) 1,500ms가 경과하기 전에 취소되지 않는다고 가정하면, 함수가 호출되고, 메뉴는 사라진다. 그러나 onmouseover 이벤트가 발생하면(메뉴 위에 마우스가 오면) clearTimeout(tout)가 실행되고 이것은 다음번에 사용자가 마우스 이벤트를 다시 발생시킬때까지 타이머를 취소시켜서 메뉴가 사라지는 것을 막는다.
메뉴 자체 내에서 다른 작업을 수행하거나 다른 페이지로 이동하는 링크를 가질 수 있다. 물론 이 예제에서는 단순히 의미없는 링크들이다. 그러나 각 메뉴 옵션에 onclick 이벤트를 추가할 수 있음을 기억하자. 위의 div 태그에서 메뉴 아이템 중 하나를 클릭하는 순간 메뉴가 사라지게 할 수 있다. (시도해 보라!) 이때 timeout은 사용하지 않는다. 이 경우에는 즉시 사라지게 해야 하기 때문이다.
다음으로 id가 testmenuroot인 span 태그로 둘러싸인 HTML 조각은 메뉴가 실제로 나타나야 할 클릭할 수 있는 영역을 둘러싼다. 예제에서는 단순히 span 태그 사이에 X로 표시했지만, 버튼, 이미지 등등 원한다면 무엇이든지 여기에 놓을 수 있다. 여기서 명심해야할 다른 것이라면 단지 onclick 이벤트인데, 이것은 메뉴를 나타나게 한 후, 그것을 다시 1,500ms 후에 끌 타이머를 설정한다. 그러나 이미 알고 있겠지만 사용자가 마우스를 메뉴 영역 위에서 움직이는 한 이 타이머는 취소될 것이고 메뉴는 마우스가 다시 사라질때까지 계속 유지될 것이다.
마지막으로 PopupMenu 자바스크립트 함수 자체를 살펴보자. 이것은 약간의 다른 함수들에 의존한다: GetAbsWindowBottom는 브라우저 독립적인 방식으로 브라우저 윈도 하단의 절대 위치를 단순히 얻어내며, getElementAbsPosX과 getElementAbsPosY는 절대 좌표에서의 엘리먼트의 x, y 위치를 얻어낸다. 마지막 두 함수는 DocumentRoot에 도달할때까지 부모 엘리먼트의 거리를 재귀적으로 더하면서 동작한다. 이 함수들은 전체적으로 복잡해 보이지만, 현재의 거의 모든 브라우저에서 작동하는 값들을 신뢰성있게 얻어 낼 수 있는 거의 유일한 방법인 것 같다. 불행히도 각각의 브라우저들은 이러한 정보들을 서로 매우 다른 방법들로 저장한다.
PopupMenu 함수 자체는 name과 메뉴가 켜질지 꺼질지를 나타내는 flag 이렇게 두개의 인자를 가진다. 메뉴는 인자로 전달된 name뒤에 "menu"가 붙은 id를 가진 태그에 의해 둘러싸인다. (예제에서는 인자가 "test"이므로 "testmenu"가 될 것이다). 마찬가지로 메뉴가 나타나야 할 위치는 name에 "menuroot"를 붙인 태그에 메뉴의 좌상단이 위치하여 나타나게 된다. (예제에서는 "testmenuroot"). vis 인자가 0이면 단순히 메뉴 태그의 visibility 스타일을 hidden으로 설정하고 끝낸다. 그렇지 않으면 메뉴가 올바른 지점에 나타나게 할 필요가 있다. 먼저 GetElementAbsPos 함수를 사용하여 "menuroot" 태그의 절대 위치를 구한다. 그러면 메뉴가 뜨게하기 위해 클릭한 위치 근처에 메뉴의 좌상단 모서리가 위치하여 나타나게 할 값들을 메뉴 위치에 지정할 수 있다. 그러나 페이지 아래로 메뉴가 스크롤 되어 사라지길 정말 원하지는 않을 것이다. 그러면 귀찮아진다. 팝업 메뉴를 가진 대부분의 위젯 툴킷은 그렇게 동작하는데, 그러므로 더 잘 작동하는 놈을 만들어야 할 것이다. 그 다음 두 줄에서 브라우저 윈도 하단과 팝업 메뉴의 하단을 절대 좌표로 계산한다. 메뉴의 높이는 clientHeight 속성으로 주어지고 약간의 패딩을 주기 위해 10을 더한다. 그러면 브라우저의 하단 밑으로 팝업이 넘어갔는지 쉽게 체크할 수 있고, 만약 넘어갔다면 그에 따라 y 좌표를 조정해서 그 하단부가 윈도 하단에 단지 닿게만 한다. 마지막으로 계속 진행하여 팝업 메뉴의 위치를 정하고 그것을 보이게 한다.
요약
이 글과 두개의 예제는 단지 몇 줄의 자바스크립트로 할 수 있는 것들의 맛보기에 지나지 않는다. 그리고 자바스크립트 툴킷 내부에 정말로 엄청나게 큰 마법이 작용하는 것도 아님을 보여준다. 비록 가끔은 트릭을 사용하지만, 보통은 모든 주요 브라우저에서 작동하는 함수를 만들수 있게 하고 추가적인 노력은 거의 안들게 한다는데에 거의 항상 가치가 있다. 좀더 자세한 자바스크립트 위젯 예제는 다음의 기사에서 알아보도록 하겠다.
저자 Howard Feldman은 몬트리올의 Chemical Computing Group에서 연구를 하는 과학자이다.