Статьи

Понимание класса Уокер

Пункты меню, страницы и (иерархические) таксономии являются примерами данных с древовидной структурой: у терминов могут быть родители, дети и братья и сестры. Обычно мы хотели бы отразить эту структуру в разметке HTML. Например, для отображения меню мы хотим, чтобы HTML представлял собой список ссылок «верхнего уровня» с вложенными списками их дочерних элементов, которые сами содержат вложенные списки своих дочерних элементов и так далее. Этот учебник проведет вас через класс WordPress, который делает создание этой разметки чрезвычайно простым.


Класс Walker — это абстрактный класс, разработанный для помощи в прохождении и отображении элементов, имеющих иерархическую (или древовидную) структуру. На самом деле он ничего не «делает» (в смысле генерации HTML). Он просто отслеживает каждую ветвь вашего дерева: он должен быть расширен другими классами, которые сообщают ему, что делать с каждым элементом, с которым он сталкивается. WordPress предоставляет свои собственные расширяющие классы, такие как:

  • Walker_Nav_Menu — для отображения HTML для меню навигации
  • Walker_Page — для отображения списка страниц
  • Walker_Category — для отображения списка терминов таксономии.

Каждый из этих классов расширяет класс Walker, просто диктуя, что класс выводит на каждом элементе и уровне дерева. Чтобы де-мистифицировать этот класс, мы рассмотрим его основные методы и пару примеров того, как его использовать. Сам класс можно найти здесь .


1
walk( $elements, $max_depth)

Класс walker запускается с помощью метода walk, и именно этот метод возвращает HTML после его генерации. Он принимает два аргумента:

  1. Массив элементов, которые мы хотим отобразить, которые будут иметь своего рода родительско-дочерние отношения
  2. $max_depth — устанавливает, сколько поколений мы исследуем
  3. Хорошо 3 … Если вы поцарапаете поверхность этого метода, вы обнаружите, что на самом деле можете передавать дополнительные аргументы, которые собираются в массив: $args . Затем это передается другим методам в классе

Метод ходьбы выделяет элементы «верхнего уровня» — те, у которых нет родителей — и помещает их в один массив. Остальные, дочерние элементы, помещаются во второй массив, где ключ является идентификатором его родителя (это двумерный массив, так как один родитель может иметь несколько дочерних элементов):

1
2
3
4
$children_elements = array(
    ‘1’ => array() //Array of elements corresponding to children of 1,
    ‘4’ => array() //Array of elements corresponding to children of 4
);

Затем он последовательно перебирает каждый из родительских элементов и применяет метод display_element .

1
display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )

Как следует из названия, display_element отвечает за отображение элемента в нашем дереве. На самом деле, он вызывает несколько функций для этого. Эти функции намеренно оставлены пустыми в классе Walker — и именно они изменяются в расширяющихся классах, так как они определяют фактический возвращаемый HTML. Это включает:

  • start_lvl — функция, возвращающая HTML для начала нового уровня. В случае списков это будет начало нового «подсписка», и поэтому будет отвечать за возвращение <ul>
  • end_lvlend_lvl когда мы закончим уровень. В примере меню навигации эта функция отвечает за завершение подсписка закрывающим тегом списка </ul>
  • start_el — функция, отвечающая за отображение текущего элемента, на котором мы находимся. В случае меню это означает <li> и ссылку на элемент.
  • end_el — функция, вызываемая после элемента, и отображаются все его дочерние элементы. Для нашего примера меню это означает возврат закрывающего </li> .

Так что же делает display_element ? Это на самом деле, где происходит вся магия класса Уокер. Для начала давайте посмотрим, какие аргументы приводятся:

  • $element — это элемент, на котором мы сейчас находимся в нашем дереве
  • $children_elements — массив всех дочерних элементов (а не только дочерних элементов упомянутого выше элемента). Это второй массив, сформированный в методе walk а ключи — это идентификаторы родителя.
  • $max_depth — как далеко мы можем исследовать
  • $depth — насколько далеко мы находимся в данный момент
  • $args — необязательные аргументы (упоминалось ранее)
  • $output — пока что HTML. Это добавляется к тому, как мы исследуем больше дерева.

Метод display_element сначала вызывает start_el который отвечает за отображение элемента. Как именно это происходит, зависит от контекста. Для раскрывающегося меню это может быть <select> Current Item или для меню навигации это может быть <li> Current Item . Обратите внимание, что закрывающего тега еще нет. Если у этого элемента есть дочерние элементы, нам нужно сначала отобразить их так, чтобы они были вложены в этот элемент …

Затем он проверяет, есть ли у текущего элемента, на котором мы находимся, дочерние элементы и что мы не достигли максимальной глубины. Если это так, мы исследуем каждого из потомков по очереди, вызывая display_element для каждого из них (с аргументом глубины, увеличенным на единицу). Таким образом display_element рекурсивно вызывает себя, пока мы не достигнем дна.

Предположим, что мы достигли «дна» (элемента без дочерних элементов или максимальной глубины), затем он вызывает end_el который добавляет закрывающий тег. На этом текущий экземпляр display_element заканчивается, и мы возвращаемся к родителю, который применяет display_element к следующему дочернему display_element , пока мы не обработаем каждый из его дочерних элементов. Когда у родителя больше нет дочерних элементов, мы возвращаемся вверх по дереву и так далее, пока каждая ветвь не будет исследована. Смущенный? Это диаграмма, которая, я надеюсь, прояснит ситуацию:


Использование класса Walker делает отображение пользовательских иерархических данных очень простым. Предположим, у вас есть массив объектов со свойствами « label », « parent_id » и « object_id », список которых вы хотите отобразить. Теперь это можно легко сделать с помощью очень простого класса:

Примечание . Расширяющий класс отвечает за установку идентификатора элемента и его родительского элемента.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Walker_Simple_Example extends Walker {
 
    // Set the properties of the element which give the ID of the current item and its parent
    var $db_fields = array( ‘parent’ => ‘parent_id’, ‘id’ => ‘object_id’ );
 
    // Displays start of a level.
    // @see Walker::start_lvl()
    function start_lvl(&$output, $depth=0, $args=array()) {
        $output .= «\n<ul>\n»;
    }
 
    // Displays end of a level.
    // @see Walker::end_lvl()
    function end_lvl(&$output, $depth=0, $args=array()) {
        $output .= «</ul>\n»;
    }
 
    // Displays start of an element.
    // @see Walker::start_el()
    function start_el(&$output, $item, $depth=0, $args=array()) {
        $output.
    }
 
    // Displays end of an element.
    // @see Walker::end_el()
    function end_el(&$output, $item, $depth=0, $args=array()) {
        $output .= «</li>\n»;
    }
}
$elements=array();
echo Walker_Simple_Example::walk($elements);

Вы можете расширить классы Walker, чтобы изменить отображаемый контент, изменить сгенерированный HTML-код или даже запретить отображение определенных веток. Функции, такие как:

Предоставьте возможность указать свой собственный класс Walker, что позволит вам сравнительно легко изменить их внешний вид, указав свой собственный класс Walker. Во многих случаях на самом деле проще расширить соответствующее расширение Walker, чем сам класс Walker.

Предположим, вы хотите иметь вторичное (под) меню, которое связано с вашим основным меню. Это может принимать форму ссылок, которые расположены чуть ниже основного меню или на боковой панели, на которой отображаются только элементы меню «потомок» текущей «страницы верхнего уровня». В качестве примера на диаграмме выше, если мы на подстраницах «Архив», «Автор» или «Новости», мы хотели бы показать все ссылки под «Архив». Поскольку Walker_Nav_Menu делает большую часть того, что мы хотим, мы расширим этот класс, а не класс Walker. Это экономит нам много усилий, так как Walker_Nav_Menu добавляет соответствующие классы (‘ current ‘, ‘ current-ancestor ‘ и т. Д.) К соответствующим ссылкам. Мы расширим класс Walker_Nav_Menu чтобы немного изменить логику и запретить отображение любых ссылок верхнего уровня или любых потомков страниц без полномочий root.

Прежде всего, в ваших файлах шаблонов мы будем использовать wp_nav_menu() дважды, указывая на одно и то же местоположение темы (я назову ее « primary »). Если у вас нет зарегистрированного местоположения темы, вам следует прочитать эту статью . Какую бы тему вы ни использовали, вам следует сохранить меню в этом месте. Мы отобразим это меню дважды. Во-первых, везде, где вы хотите, чтобы ваше меню верхнего уровня появилось:

1
wp_nav_menu( array(‘theme_location’=>’primary’,’depth’ => 1) );

Опять же, с помощью пользовательского обходчика, чтобы отобразить только (соответствующие) дочерние страницы.

1
wp_nav_menu( array(‘theme_location’=>’primary’,’walker’ => new SH_Child_Only_Walker(),’depth’ => 0) );

Прежде всего, мы не хотим показывать родителей высшего уровня. Напомним, что функция, отвечающая за открывающий <li> и ссылку, — start_el а функция, отвечающая за закрывающий </li>end_el . Мы просто проверяем, находимся ли мы на родительском уровне. Если мы, мы ничего не делаем. В противном случае мы продолжаем «как обычно» и вызываем функцию из класса Walker_Nav_Menu .

01
02
03
04
05
06
07
08
09
10
11
12
// Don’t print top-level elements
function start_el(&$output, $item, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::start_el(&$output, $item, $depth, $args);
}
 
function end_el(&$output, $item, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::end_el(&$output, $item, $depth, $args);
}

display_element . Эта функция отвечает за путешествие по веткам. Мы хотим остановить это в своих треках, если мы находимся на верхнем уровне, а не на текущей корневой ссылке. Чтобы проверить, является ли ветвь, в которой мы находимся, «текущей», мы проверяем, имеет ли элемент какой-либо из следующих классов: « current-menu-item », « current-menu-parent », « current-menu-ancestor ».

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// Only follow down one branch
function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
 
    // Check if element as a ‘current element’ class
    $current_element_markers = array( ‘current-menu-item’, ‘current-menu-parent’, ‘current-menu-ancestor’ );
    $current_class = array_intersect( $current_element_markers, $element->classes );
 
    // If element has a ‘current’ class, it is an ancestor of the current element
    $ancestor_of_current = !empty($current_class);
 
    // If this is a top-level link and not the current, or ancestor of the current menu item — stop here.
    if ( 0 == $depth && !$ancestor_of_current)
        return;
 
    parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
}

Теперь мы расширим функции start_lvl и end_lvl . Они отвечают за вывод HTML-кода, который оборачивает уровень (в данном случае теги <ul> ). Если мы находимся на верхнем уровне, мы не хотим отображать эти теги (ведь содержимое не будет отображаться).

01
02
03
04
05
06
07
08
09
10
11
12
// Don’t wrap the top level
function start_lvl(&$output, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::start_lvl(&$output, $depth, $args);
}
 
function end_lvl(&$output, $depth=0, $args=array()) {
    if( 0 == $depth )
        return;
    parent::end_lvl(&$output, $depth, $args);
}

Этот класс в полном объеме:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class SH_Child_Only_Walker extends Walker_Nav_Menu {
 
    // Don’t start the top level
    function start_lvl(&$output, $depth=0, $args=array()) {
        if( 0 == $depth )
            return;
        parent::start_lvl(&$output, $depth,$args);
    }
 
    // Don’t end the top level
    function end_lvl(&$output, $depth=0, $args=array()) {
        if( 0 == $depth )
            return;
        parent::end_lvl(&$output, $depth,$args);
    }
 
    // Don’t print top-level elements
    function start_el(&$output, $item, $depth=0, $args=array()) {
        if( 0 == $depth )
            return;
        parent::start_el(&$output, $item, $depth, $args);
    }
 
    function end_el(&$output, $item, $depth=0, $args=array()) {
        if( 0 == $depth )
            return;
        parent::end_el(&$output, $item, $depth, $args);
    }
 
    // Only follow down one branch
    function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
 
        // Check if element as a ‘current element’ class
        $current_element_markers = array( ‘current-menu-item’, ‘current-menu-parent’, ‘current-menu-ancestor’ );
        $current_class = array_intersect( $current_element_markers, $element->classes );
 
        // If element has a ‘current’ class, it is an ancestor of the current element
        $ancestor_of_current = !empty($current_class);
 
        // If this is a top-level link and not the current, or ancestor of the current menu item — stop here.
        if ( 0 == $depth && !$ancestor_of_current)
            return
 
        parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
    }
}

Как только вы поймете, как работает класс Walker, вы можете расширить его (или существующие расширения WordPress), чтобы изменить способ отображения ваших иерархических данных. Например, вы можете:

  • Включите описания со ссылками на меню или описаниями категорий.
  • Исключить целые ветки меню для вышедших из строя пользователей.
  • Включить мета-пост в свой список страниц.