워드프레스 메뉴에서 sub-menu를 가진 경우엔 has-child 클래스 붙여 주기

워드프레스의 ‘메뉴’ 기능을 사용하면서 아쉬운 게, 1단계 li와 2단계 li를 구분하는 클래스가 없다. 그래서 계층형 클래스를 사용하게 된다. sub-menu를 가진 li와 가지지 않은 li도 구분이 안 된다.

그래서 일단, 1단계 limain-nav__li1이라는 클래스를 붙였다. 2단계 li에까지 붙이는 건 시간 관계상 안 했다. 또한 sub-menu를 가진 li에는 main-nav__li1--has-child라는 클래스를 붙이도록 만들었다.

이 과정을 공유한다. 이제부터 시작.

작업을 하고 나면 아래처럼 메뉴를 호출하게 된다. (파일은 header.php)

$opt = array(
    // ...
    'walker' => new MBT_Walker_Nav_Menu,
);
wp_nav_menu($opt);

walker라는 옵션에 주목하자. 위 옵션은 쭈욱 돌면서 메뉴를 뿌려 주는 역할을 하는 객체를 넘겨 주는 옵션이다. 웬만해선 건드릴 일이 없다.

전에 한 번 이걸 고치는 예제를 본 적이 있는데 ‘이게 도대체 뭐야?’ 하는 생각만 하고 볼 엄두를 못 낸 적이 있다. 그런데 오늘 옵션 쪽을 뜯어 보다 보니, 내가 자세히 보지 않아서 이해를 못 했던 것이라는 걸 깨달았다. 객체를 우리가 직접 짜야 하는 게 아니었다. 복사해서 붙여넣고 조금만 수정해 주면 되는 거였다.

기본 walker를 뜯어 보자

wp-includes/nav-menu-template.php 파일에 있는 wp_nav_menu 함수를 살펴 보자. walker 옵션의 기본값은 그냥 빈 문자열이다. 뭐지?

219번째 줄에 가 보면 비밀을 알 수 있다(이하 모든 줄번호는 WP 3.6 기준이다).

$items .= walk_nav_menu_tree( $sorted_menu_items, $args->depth, $args );

walk_nav_menu_tree라는 함수에 세 값을 넘겨 주는데, $args가 우리가 세팅한 옵션값을 갖고 있는 놈이다. 우린 배열로 세팅해서 넘겼지만, 중간에 객체 형식으로 바뀌어서, 우리가 넘겨 준 walker 값은 $args->walker에 세팅돼 있다.

walk_nav_menu_tree 함수를 살펴 보자.

function walk_nav_menu_tree( $items, $depth, $r ) {
    $walker = ( empty($r->walker) ) ? new Walker_Nav_Menu : $r->walker;
    $args = array( $items, $depth, $r );

    return call_user_func_array( array($walker, 'walk'), $args );
}

간단하게 돼 있다. 세 번째 인자값인 $r이 바로 $args다. 위 코드의 두 번째 줄을 보면 $walker를 세팅하는 게 나온다. 비어 있으면 Walker_Nav_Menu 객체를 생성해서 받게 돼 있다. 그렇게 $walker라는 변수에 객체를 세팅한 뒤, 3번째 줄에서 $args를 세팅해서 call_user_func_array 함수로 $walker->walk($args)를 호출한다(5번째 줄).

즉, 메뉴를 뿌리는 함수들은 Walker_Nav_Menu 객체에 들어 있으며, 이걸 교체해 주면 된다는 말이다. 이제 Walker_Nav_Menu 클래스를 뜯어 보자.

Walker_Nav_MenuWalker 클래스

Walker_Nav_Menu 클래스는 wp-includes/nav-menu-template.php 파일의 17번째 줄부터 정의돼 있다. 아래처럼 시작한다.

class Walker_Nav_Menu extends Walker {

여기서 알 수 있는 것은, Walker_Nav_MenuWalker를 상속한다는 것이다. 아까 walk 함수를 호출해서 메뉴를 그린다고 했는데, Walker_Nav_Menu에는 walk 함수가 없다. 그럼 부모 클래스인 Walker 클래스에 있을 것이다. 그리로 찾아 가 보자.

Walker 클래스는 wp-includes/class-wp-walker.php 파일의 15번째 줄에 정의돼 있다. walk 함수도 갖고 있다. 174번째 줄이다.

walk 함수를 뜯어 보다가 230번째 줄에서 찾던 걸 찾았다. 아래 코드다.

foreach ( $top_level_elements as $e )
    $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );

탑 레벨 요소면 display_element 함수에 $children_elements를 넣어서 뿌리게 되는 거다. 와우!

$e가 바로 탑 레벨 요소의 정보고, $e->classes 배열에 클래스 이름이 들어간다. 좋아! 여기를 고칠까? 아니다. 여긴 워드프레스 코어 파일이다. 코어 파일을 직접 수정하면 워드프레스를 버전업할 때 내가 수정한 게 사라진다. 테마에서 해결해야 한다.

지금 고쳐야 하는 건 walk 함수니까, Walker_Nav_Menu 클래스를 상속받는 새로운 클래스를 만들어서 wp_nav_menu 함수의 walk 옵션에 넘겨 주면 되겠다. 클래스를 만들어 보자.

// Walker_Nav_Menu 클래스를 상속. MBT는 Mytory Basic Theme.
class MBT_Walker_Nav_Menu extends Walker_Nav_Menu{

    // 주석은 생략
    function walk( $elements, $max_depth) {
        // walk 함수를 그대로 복사했다.
    }
}

이렇게 간단한 구조다. 나는 functions-nav.php라고 파일을 만들어서 위 코드를 넣었고, functions.php에서 인클루드했다.

다음으로 할 일은, MBT_Walker_Nav_Menu 클래스의 walk 함수 일부를 수정하는 것이다. foreach ( $top_level_elements as $e ) 부분을 찾아가서 두 줄을 아래처럼 수정했다. 도움을 주기 위해 주석을 달았다. 실제 코드엔 이런 주석 없다.

foreach ( $top_level_elements as $e ){

    // 탑 레벨이니까 1번째 li임을 나타내기 위해 li1이라고 클래스 붙임.
    $e->classes[] = 'main-nav__li1';

    // $children_elements가 있으면 추가로 has-child라고 클래스 붙임.
    if( ! empty($children_elements[$e->ID])){
        $e->classes[] = 'main-nav__li1--has-child';
    }

    // 아래는 원 코드와 같음.
    $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
}

오케이. 이렇게 하고 나서 아래처럼 메뉴를 호출해 주면 된다.

$opt = array(
    'theme_location' => 'main-nav', 
    'walker' => new MBT_Walker_Nav_Menu,
);
wp_nav_menu($opt); 

그러면 모든 탑 레벨 li에는 main-nav__li1이라는 클래스가 붙고, sub-menu를 가진 li에는 main-nav__li1--has-child라는 클래스가 추가로 붙는다.

이제 나머지 응용을 하면 된다.

카테고리 글 목록 👉

대표글

댓글 남기기