jQuery : 탐색(Traversing) 메서드

필자의 잡담~

갈수록 인기를 얻어가는 태오의 jQuery 강좌.
7월에는 세미나로도 만나보실 수 있지 않을까 합니다. ㅎㅎ

이번 강좌는 트래버스(Traverse)와 관련된 강좌입니다. 트래버스라는 단어의 의미는 어떤 것을 횡단하거나, 탐색하거나, 검토하거나, 여기 저기 왔다갔다 하는 등의 의미를 갖는 단어입니다. 즉, jQuery의 트래버스 관련 메서드들은 개체들의 집합에서 다시금 특정 개체를 찾거나, 필터하거나, 추가하는 등의 작업을 위해 지원되는 API 메서드들의 그룹이라고 보시면 됩니다. 사실, 개체를 찾거나 필터하는 작업은 대부분 셀렉터를 통해서 이루어지지만, 1차적으로 셀렉터를 통해서 필터링을 하고 난 다음에, 그 결과 집합 내에서 추가적으로 필터링하거나, 추가 탐색하거나, 다른 결과 집합과 합치거나 하는 등의 작업을 하기 위해서는 이러한 메서드가 유용하게 사용됩니다.

트래버스와 관련된 많은 메서드들이 존재합니다만, 이들 전부에 대해서 이야기하는 것은 다소 어려울 것 같고요. 이전 강좌들과 마찬가지로 자주 사용하는 메서드들 중심적으로 설명을 드려볼까 합니다. 전체 트래버스 메서드는 http://docs.jquery.com/Traversing에서 살펴보실 수 있으니 완전한 목록은 정식 문서를 참고하시길 바라며언셔~~ 시작해 보겠습니다.

우선, 추가 필터링 관련 메서드들부터 알아보도록 하겠습니다. 다시 말씀드리지만, 이들은 모두 셀렉터를 통해서 1차적으로 일치되는 집합을 얻어낸 후, 그 집합에 대해서 사용할 수 있는 메서드들입니다.

eq(index) 일치된 요소들 중에서 인덱스와 일치하는 단일 요소를 가져옵니다.
filter(expr) 지정된 표현식과 매치되지 않는 모든 요소들을 제거합니다. 즉, 매치되는 요소들만을 가져옵니다.
filter(func) 지정된 함수와 매치되지 않는 모든 요소들을 제거합니다.
is(expr) 현재 개체와 표현식에 해당한다면 true, 표현식에 여러 개의 조건이 있다면 그 중 한 개만 맞아도 true가 됨
map(callback) jQuery 개체 안에 있는 요소들의 집합을 다른 집합으로 변경해서 옮긴다.
not(expr) 지정된 표현식과 매치되는 모든 요소들을 제거합니다. 즉, 매치되지 않는 요소들만을 가져옵니다.

이 중 특히나, filter 메서드와 is, not 메서드는 활용빈도가 높습니다. 셀렉터에 의해서 1차적으로 걸러낸 데이터를 가지고 작업을 하다가 2차적으로 추가 필터링을 한다거나, 특정 요소가 어떤 조건에 부합하는지를 검사한다거나 하는 작업은 꽤나 자주 하게되는 일이니까요.

메서드의 인자인 expr은 expression을 의미하는 것으로 여기에는 주로 셀렉터를 사용합니다.

그러면, 각각의 항목에 대해서 조금씩만 더 설명을 하고, 예제로 들어가 보도록 하겠습니다.

eq(index)라는 메서드는 사실 :eq(index) 라는 셀렉터 필터와 거의 동일합니다. 하나는 메서드이고 하나는 셀렉터 구문이라는 차이만 있는 것이죠. 다음 예제 코드를 한번 보세요.

$("div:eq(1)").addClass("blue");
$("div").eq(1).addClass("blue");

첫 번째 구문은 이미 익숙한 셀렉터이죠? 다들 아시겠지만, 현재 문서에 있는 div 중 2번째로 등장하는 div를 얻어내는 셀렉터입니다. 그리고, 그 요소에 대해 blue 라는 css 클래스를 적용하고 있습니다.

이는 eq() 메서드를 사용하는 두 번째 구문처럼 작성할 수도 있습니다.일단 모든 div들을 셀렉트한 다음, 그 중 2번째 개체를 얻어내기 위해서 eq(1)이라는 메서드를 사용하는 것이죠. 사실, 이 둘은 결과적으로 동일합니다. 그렇다면, 어떤 방법을 쓰던지는 우리의 맘일까요? 그럼요~ 여러분의 마음입니다. 다만!!!

eq() 메서드를 사용한 경우에는 end()라는 엄청난 초능력자 메서드의 도움을 받아 이전 상태로 돌아갈 수 있다는 장점이 있습니다(eq() 메서드 뿐만 아니라 대부분의 조작 메서드는 end() 메서드를 사용할 수 있습니다).

원래 end() 메서드는 이번 강좌 후반에 설명을 드릴 예정이었던 메서드이긴 합니다만, 말이 나온 김에, 그리고 여기가 보다 적절한 자리인 것 같기에 앞당겨 설명을 드려봅니다.

end() 메서드 :
현재 일치된 개체 집합을 변경하여, 방금 일어난 "파괴적인 작업" 직전의 상태로 되돌린다.

마치 1분 전의 과거로 돌아갈 수 있는 초능력자를 보는 듯한 표정을 지으시면 됩니다. 이 녀석은 그런 능력을 가지고 있으니까요. 대부분의 조작 메서드, 트래버스 메서드에 end()라는 메서드를 적용할 수 있습니다. 이는 자기가 되돌릴 과거 상태가 존재한다면, 현재의 개체집합을 그 상태로 되돌리고, 되돌릴 과거없는 상태라면 단순히 빈 값을 반환합니다.

앞의 예제에 이를 사용해 보기 위해서, 다음과 같은 조건에 맞는 셀렉터를 한번 작성해 보도록 해요.

"모든 짝수 번째 div를 구한 다음, 그 중 첫 번째 div는 orange라는 배경색을 적용하고, 두 번째 div는 blue 배경색을 적용하라"

end() 메서드를 모르는 상황이었다면 여러분은 이를 다음과 같이 작성할 듯 합니다.

$("div:odd").eq(0).css("background""orange");
$("div:odd").eq(1).css("background""blue");

우선, $("div:odd")를 통해서 짝수번째 div들을 구한 다음, eq() 메서드를 사용해서 그 중 첫 번째와 두 번째 요소에 각각 접근하는 것이죠. 코드가 두 줄이라는 것은 그렇다치더라도, 각각에 대해서 동일한 검색을 두번이나 반복하는 부분은 썩 마음에 들지 않습니다.

해서, 이 코드를 다음과 같이 할 수 있으면 좋을 것 같다는 생각이 들지 않나요?

$("div:odd")
            .eq(
0).css("background""orange")
            .eq(
1).css("background""blue");

그러면 참 좋겠습니다만, 안타깝게도 이는 올바로 동작하지 않습니다. 왜냐하면, 이미 $("div:odd").eq(0)으로 인해서 매치되는 집합이 전체 div:odd가 아니라 단일 요소로 줄어든 상황에서는 eq(1)을 해봐야 아무런 대상도 찾을 수 없기 때문이죠. 그렇다면, 최종 요소에 대해 작업을 하고 방금 이전의 상태로 돌아가서 다시 검색할 수 있다면 얼마나 좋을까 하는 생각이 들지 않나요? 들었죠? (그렇다면, 내려 놓으세요~ 아. 몹쓸 개그였습니다)

바로 여기서 end()가 엄청난 능력을 발휘하게 됩니다. 상기 코드는 다음과 같이 작성할 수 있습니다.

$("div:odd")
            .eq(
0).css("background""orange")
            
.end()     // $("div:odd")와 동일
            
.eq(1).css("background""blue");

여기서 end()는 eq(0)을 하기 직전의 상태로 되돌리는 역할을 합니다. 고로, end()를 사용하면 단 한줄의 메서드 체인으로 원하는 작업을 마무리 할 수 있게 되는 것입니다. 멋지죠?

end()는 jQuery에서 대단히 심도있게 사용되므로, 잘 기억을 해두시기 바랍니다. 하긴, 기억을 하고 싶지 않아도 저절로 하실 수 밖에 없게 될 듯 해 보이네요. 하하

그럼, 이제 필터 메서드에 대해서 알아보도록 하죠.

filter(expr)은 결과적으로는 일반 셀렉터를 통한 검색하는 것과 동일하다고 보시면 됩니다. 다만, 셀렉터는 문서상에 존재하는 개체들을 대상으로 검색한다면, 이는 메모리상에 존재하는 개체 집합을 대상으로 필터링을 한다는 부분에서 차이가 있습니다. 즉, 셀렉터에 의한 1차적 검색 집합에 대해서 다시 추가적으로 필터링(걸러냄)을 하고 싶은 경우에 사용하는 API 메서드라는 것이죠. 인자로는 필터링을 위한 표현식을 제공할 수 있고요. 그 표현식과 매치되지 않는 모든 요소들을 개체 집합(메모리)에서 제거하여 필터링된 결과를 얻습니다.

반면, 유사한 API로 Filter(func)도 있는데요. 이는 인자로 표현식이 아닌 함수를 사용하지요. 즉, 필터링(걸러냄)을 함수를 통해서 보다 세밀하게 제어하고 싶은 경우에 사용합니다. 필터링 조건이 일반적인 표현식으로 설명하기 어려운 경우에 별도의 함수로 작성을 해서 그 안에서 어떻게 필터를 수행할 것인지를 지정하는 것이죠.

가장 간단하게 예를 들면, 다음 2개의 표현은 결과적으로는 동일하다고 볼 수 있습니다.

$("div:odd"
$(
"div").filter(":odd")

다만, 앞의 예제와 마찬가지로 filter 메서드를 사용한 경우는 end() 메서드의 도움을 얻어 이전 상태로 되돌릴 수가 있다는 장점이 있죠.

예를 들면, 다음과 같이 말입니다. 다음 코드는 앞선 예제에서 $("div:odd")를 $("div").filter(":odd")로 변경해 본 것입니다.

$("div").filter(":odd")
    .eq(
0).css("background""orange")
    .end()     
// filter(":odd")
    
.eq(1).css("background""blue")
    .end()     
// filter(":odd")
    
.css("color""red");   //이 코드는 odd인 div들의 폰트색상을 red로 적용한다.

이는 셀렉터를 사용하여 전체 div를 1차적 선택한 다음에 filter() 메서드를 사용하여 그 중 짝수번째 인 것들만 다시 필터링을 하고요. 그 짝수번째 div들 중에서 첫 번째 녀석(eq(0))은 오렌지 배경색으로 설정하고, 다시 짝수번째 div들 중 두번째 녀석(eq(1))은 파란 배경색으로 설정하고, 마지막으로 모든 짝수번째 div에 대해서 빨간 폰트를 지정하고 있습니다.

end() 메서드는 한번만 가능한 것이 아니라 메서드 체인 내에서 계속적으로 사용하여 방금 전 상태로 계속 올라갈 수 있습니다. 대단하죠?

또한, filter(expr) 메서드와 반대되는 메서드로 not(expr) 메서드도 있습니다. Filter(expr) 메서드가 현재의 집합에서 expr 표현식과 매치되는 것만을 가져온다면, not(expr) 메서드는 현재의 집합에서 expr 표현식과 매치되지 않는 것들만을 가져옵니다. 그렇기에, 다음 두 표현식은 동일한 개체 집합을 갖게 된다고 볼 수 있습니다.

$("div").filter(":odd")
$(
"div").not(":even")

하하. 재미있네요. 저만 재미있나요?

또한, 중요한 메서드로 is(expr)도 있습니다. 이는 그 이름이 의미하듯이, 개체를 비교하여 true/false를 알려주는 메서드인데요. 예를 들어, 다음의 코드를 한번 살펴보시죠.

var $myDiv $("div").eq(5);
if 
($myDiv.is("div")) 
{
  $myDiv.css(
"border""4px solid yellow");
}

이 코드는 우선 div 중 6번째 요소를 가져와서 $myDiv라는 변수로 참조하고 있습니다. 일단, 요 부분에서도 드릴 말씀이 하나 있는데요. 일반적으로 jQuery에서는 jQuery 개체를 일시적으로 참조하는 변수명에는 $로 시작하는 변수명을 쓰도록 은근히 권유하고 있습니다. 그래야 일반 변수들과 jQuery 개체변수가 쉽게 구분되기 때문이지요. 물론, 꼭 그래야 하는 것은 아니지만 가독성이 좋아지니 가급적 그렇게 하시길 권해봅니다.

어쨌든, 위의 소스는 $myDiv 변수에 6번째 div를 참조한 뒤, is(expr) 메서드를 사용해서 그 개체가 "div" 개체인지를 비교하여, 만일 "div"가 맞다면, 그 div에 대해서는 노란색의 두꺼운 테두리를 갖도록 지정하고 있지요.

expr인자에는 일반적으로 "div"처럼 단일 구문을 작성하곤 하지만, ","를 구분자로 사용하여 여러 개의 표현식을 나열할 수도 있습니다. 다음과 같이 말이죠.

$myDiv.is(".orange, .blue, .lightblue")

이는 $myDiv가 orange나 blue, lightblue css 클래스 중 하나라도 가지고 있으면 true가 됩니다. ^^

그리고, 그리 자주 사용되는 메서드는 아닙니다만 map(callback)라는 것이 있지요. 사실, 이는 생각보다 사용빈도가 적어서 아주 자세하게 설명하지는 않겠지만(자세한 설명은 공식 사이트에서 샘플 코드를 살펴보시면 쉽게 이해가 될 겁니다), jQuery 개체 안에 있는 요소들의 집합을 다른 집합으로 변경해서 옮긴다는 것은 기억해 두시기 바랍니다. 예를 들어, div 안에 각각 a부터 z 까지 소문자 알파벳이 기록 되어 있는 상황에서 다음과 같은 코드를 사용한다면

var arr $("div").map(function() {
   
return $(this).text().toUpperCase();
});

arr 이라는 변수에는 A부터 Z 까지 대문자로 구성된 배열이 채워진다는 것이죠. 셀렉터로 선택된 개체들의 집합에서 원하는 데이터를 별도의 다른 집합으로 옮길 수 있다는 것이 이 메서드의 특징입니다. ^^

자. 그럼 지금까지 설명한 것이 모두 포함된 예제를 한번 같이 해볼까요? 다음은 Traverse.htm 페이지의 소스입니다.

<html xmlns="http://www.w3.org/1999/xhtml" >
  
<head runat="server">
    
<title></title>
    
<script src="jquery-1.3.2.min.js" type="text/javascript"></script>
      
<script>
          $(
document).ready(function() {

              $(
"div").eq(0).addClass("lightblue");

              
$("div").filter(":odd")
                .eq(
0).css("background""orange")
                .end()
                .eq(
1).css("background""blue")
                .end()
                .css(
"color""red");

              var 
$myDiv $("div").eq(5);
              if 
($myDiv.is("div")) $myDiv.css("border""4px solid yellow");
              if 
($myDiv.is(".orange, .blue, .lightblue")) $myDiv.text("칼라");

              var 
arr $("div").map(function() {
                  
return $(this).text().toUpperCase();
              
});
          
});
    </
script>
    
<style>
      div 
{ width:60px; height:60px; margin:10px; float:left; 
           border
:2px solid blue  ; }
      
.blue { bac  kground:blue; }
      
.lightblue { background:lightblue; }
      
.orange { background:orange; }
    </
style>
  
</head>
<body>
  
<div>a</div>
  
<div>b</div>
  
<div>c</div>
  
<div>d</div>
  
<div>e</div>
  
<div class="orange">f</div>
  
<div>g</div>
</body>
</html>

그리고, 다음은 이 페이지의 결과 화면이고요. 이미 앞서서 설명드린 코드들이 들어있는 것이기에 별도의 소스 설명은 필요없을 듯 합니다 ^^

이 부분을 꽤나 길게 설명드린 것은 그만큼 자주 사용하기 때문이니까요. 꼭 각 구문의 역할을 확실히 이해하시기를 부탁드립니다.

그러면, 이번에는 트래버스 관련 메서드들 중 찾기와 관계된 API들을 살펴볼까요?(앞서 말한 바와 같이 이 목록은 전체 목록이 아닙니다. 자주 사용하는 일부만을 담고 있으니 전체 목록은 공식 문서인 http://docs.jquery.com/Traversing에서 확인하세요)

find(expr) 지정된 표현식과 일치하는 요소를 검색한다.
add(expr) 현재 요소에 expr와 일치하는 요소를 추가한다. 즉, 일치되는 요소가 추가 확장되는 의미이다.
next(expr) 현재 요소 바로 다음에 나오는 형제 요소를 선택한다. expr 을 지정하면 그로 필터링을 수행한다
nextAll(expr) 현재 요소 바로 다음에 나오는 모든 형제 요소들을 선택한다. expr 을 지정하면 그로 필터링을 수행한다
parent(expr) 현재 요소의 부모를 선택한다. expr 을 지정하면 그로 필터링을 수행한다
parents(expr) 현재 요소의 고유한 부모 요소들을 선택한다. expr 을 지정하면 그로 필터링을 수행한다
prev(expr) 현재 요소보다 앞서 나오는 형제 요소를 선택한다. expr 을 지정하면 그로 필터링을 수행한다
prevAll(expr) 현재 요소보다 앞서 나오는 모든 형제 요소들을 선택한다. expr 을 지정하면 그로 필터링을 수행한다
siblings(expr) 현재 요소의 모든 형제 요소들(자신은 제외)을 선택한다. expr 을 지정하면 그로 필터링을 수행한다

메서드 명칭이 직관적이어서 각각의 API의 역할이 쉽게 이해가 될 듯 합니다.

가장 재미있는 메서드는 그 이름 때문에 오해하기 쉬운 add(expr)인데요. 이는 셀렉터에 의해 1차적으로 검색된 결과 집합에 추가적인 검색 집합을 합치는(add) 역할을 합니다. 말로 하니 설명이 좀 모호한데요. 다음 코드를 한번 보시죠.

$("div").add("p").css("background""yellow");

add란 이름 때문에 얼핏 보면, 마치 div 요소 안에다가 p 요소를 생성하여 추가하는 것으로 느껴질 수 있는데요. 사실은 검색 결과가 합쳐지는 것입니다. 즉, $("div").add("p") 라는 것은 $("div, p")와 같다고 볼 수 있다는 것이죠. 해서, 다음과 같은 구문은

$("div")
        .css(
"border""1px solid red")
        .add(
"p")
        .css(
"background""yellow");

모든 div에게는 붉은색 테두리를 주고, div에 더하여 모든 p 요소들에게 노란 배경색을 주는 표현이 됩니다. 즉, 모든 div와 p는 노란 배경색을 갖지만, 붉은 테두리는 div만 갖게 되는 것이죠. ㅎㅎ

그 다음으로 이야기할 만한 것은 find(expr) 인데요. 이는 현재 검색된 요소의 자식을 대상으로 추가적인 검색을 수행합니다. 가끔 어떤 분들은 filter()와 find()를 헛갈려 하시더군요. 둘의 차이는 사실 아주 간단합니다. filter()는 검색된 결과 집합에서 그 개체들에 대해서 다시금 필터링을 하는 것이고요. find()는 검색된 결과 집합에서 찾는 것이 아니라, 결과 집합 내의 각 요소의 "자식"에게서 찾는 겁니다. 그래도 어렵다면, 다음의 차이를 구분하실 수 있으면 되는데요.

$("div:odd").filter("p")
$(
"div:odd").find("p")

위의 코드 중 첫번째 구문인 filter 구문의 결과는 "없음" 입니다. 왜냐구요? 셀렉터에 의해서 짝수번째 div 들만 검색한 다음, 그 div들 중에서 p라는 태그를 갖는 놈들을 가져올라고 하니 있을리가 없죠. 모두 div 태그들만 있는 집합에서 뜬금없는 p 태그로의 필터링은 뭔가요? 당황스럽죠? ㅎㅎ. 해서, filter() 메서드에서 인자로 요소명을 주는 경우는 사실 거의 없죠.

반면, find()에 의한 결과는 아주 잘 나올 것입니다(물론, div 요소의 하위로 p요소가 있다고 가정했을 경우에 말이죠). Div들 중에 p 태그를 갖는 놈이 있다면, 그 p 요소들으 find() 명령에 의해서 수루룩 일치대상이 될 것입니다.

알고나니 쉽죠? 항상 모든 일이 그런 것 같습니다. 알기 전에는 대단한 것처럼 보이지만, 막상 알고나면 별 것 아니죠. 하지만, 알고 모름의 작은 차이가 가끔은 차등 대접의 원인이 되기도 합니다요. 그래서, 어릴때 부모님이 공부해라 공부해라 하시는 것인가 봅니다.

그 외의 메서드인 next(), prev(), nextAll() 이런 것들은 굳이 설명을 드릴 필요가 없을 것 같아요. 이름에서도 느껴질 뿐더러, 다른 언어에서도 이런 류의 메서드는 항상 있어왔고, 그 역할도 항상 똑같기 때문이죠 ^^

자. 방금 배운 것들을 그런대로 섞어본 종합 예제 하나 더 나갑니다.

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  
<title></title>
  
<script src="jquery-1.3.2.min.js" type="text/javascript"></script>
  
<script type="text/javascript">
    $(
document).ready(function() {

      $(
"div:eq(1)")
        .siblings().css(
"border""1px solid blue")
        .end()
        .next().text(
"third")
        .end()
        .nextAll().css(
"background""yellow");
                
      
$("div").find("p").css("color""blue")
        .add(
"span").css("border""1px solid red");
         
    
});
  </
script>
  
<style>
    
* { font-size:12px; font-family:돋움; }
    div 
{ width:60px; height:60px; margin:10px; float:left; 
          border
:1px solid silver; padding: 5px }
  </
style>
</head>
<body>
  
<div><p>p a</p> </div>
  
<div><p>p b</p></div>
  
<div><p>p c</p></div>
  
<div><br /><span>span d</span></div>
  
<div><br /><span>span e</span></div>
</body>
</html>

예제는 siblings()와 next(), nextAll() 그리고 find()와 add()까지 골고루 섞어서 사용하는 대단히 적절하다 못해 아니꼽기까지 한 출중한 예제가 아닐 수 없습니다.

$("div:eq(1)")
  .siblings().css(
"border""1px solid blue")
  .end()    
// $("div:eq(1)")
  
.next().text("third")
  .end()    
// $("div:eq(1)")
  
.nextAll().css("background""yellow");

라는 구문은 문서 상에 존재하는 div 중 두번째 div를 1차적으로 가져온 다음, 그와 친구들인 모든 요소들(siblings())에게 파란색 테두리를 적용하고요. end()를 사용해서 다시 두번째 div로 돌아온 다음, 자신의 바로 뒤에 나오는 요소(next())의 텍스트를 third로 바꾸고 있습니다. 그리고, 또 end()를 사용해서 두번째 div로 되돌아 온다음, 자신의 뒤로 나오는 모든 형제 요소들에게 노란색 배경색을 지정하고 있습니다.

가급적 end() 메서드 뒤에는 // 주석을 사용하여, end()로 인한 대상 집합이 현재 무엇인지를 적어두시는 좋습니다. 그러면, 보다 코드가 직관적이 될테니까요 ^^

그러면, 브라우저로 이를 실행한 결과는 어떨까요? ㅎㅎ

예상한대로의 결과가 나왔나요? 그러길 바랍니다. ^^

트래버스 강좌는 이걸로 마무리가 일단락 되었습니다. 이로써, 대부분의 jQuery의 일반적인 API 메서드들은 대부분 언급을 한 것 같긴 한데요. css 관련 메서드나 어트리뷰트 관련 메서드는 사실 언급을 제대로 하지 않았다는 것을 이제야 깨달았습니다. ㅎㅎ

해서, 다음 강좌는 그에 대한 강좌로 준비할까 생각하는데요. 한편으로는 굳이 그것을 제가 설명드릴 필요가 없어보이기도 해서, 그 부분은 자습에 맡기고 저는 이벤트나 이펙트 쪽의 설명으로 넘어갈까 고민하고 있기도 합니다.

어느쪽으로 방향을 잡을지는 여러분의 리플을 통해서 결정해 보는 것도 좋겠네요.
그러면, 오늘도 좋은 하루 되세요~~

Ps : 아참. 7월에는 jQuery의 기초편 총정리의 의미로 3시간짜리 무료 세미나를 진행할까 고민하고 있기도 합니다. MS에 세미나실을 빌릴 수 있는지 확인을 해봐야겠네요.


authored by Taeyo

Posted by 바람이불면
,